All Files ( 29.57% covered at 3.42 hits/line )
107 files in total.
5418 relevant lines,
1602 lines covered and
3816 lines missed.
(
29.57%
)
-
# frozen_string_literal: true
-
-
#--
-
# Copyright (c) 2004-2020 David Heinemeier Hansson
-
#
-
# Permission is hereby granted, free of charge, to any person obtaining
-
# a copy of this software and associated documentation files (the
-
# "Software"), to deal in the Software without restriction, including
-
# without limitation the rights to use, copy, modify, merge, publish,
-
# distribute, sublicense, and/or sell copies of the Software, and to
-
# permit persons to whom the Software is furnished to do so, subject to
-
# the following conditions:
-
#
-
# The above copyright notice and this permission notice shall be
-
# included in all copies or substantial portions of the Software.
-
#
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
#++
-
-
9
require "active_support"
-
9
require "active_support/rails"
-
9
require "action_view/version"
-
-
9
module ActionView
-
9
extend ActiveSupport::Autoload
-
-
9
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
-
-
9
eager_autoload do
-
9
autoload :Base
-
9
autoload :Context
-
9
autoload :Digestor
-
9
autoload :Helpers
-
9
autoload :LookupContext
-
9
autoload :Layouts
-
9
autoload :PathSet
-
9
autoload :RecordIdentifier
-
9
autoload :Rendering
-
9
autoload :RoutingUrlFor
-
9
autoload :Template
-
9
autoload :UnboundTemplate
-
9
autoload :ViewPaths
-
-
9
autoload_under "renderer" do
-
9
autoload :Renderer
-
9
autoload :AbstractRenderer
-
9
autoload :PartialRenderer
-
9
autoload :CollectionRenderer
-
9
autoload :ObjectRenderer
-
9
autoload :TemplateRenderer
-
9
autoload :StreamingTemplateRenderer
-
end
-
-
9
autoload_at "action_view/template/resolver" do
-
9
autoload :Resolver
-
9
autoload :PathResolver
-
9
autoload :FileSystemResolver
-
9
autoload :OptimizedFileSystemResolver
-
9
autoload :FallbackFileSystemResolver
-
end
-
-
9
autoload_at "action_view/buffers" do
-
9
autoload :OutputBuffer
-
9
autoload :StreamingBuffer
-
end
-
-
9
autoload_at "action_view/flows" do
-
9
autoload :OutputFlow
-
9
autoload :StreamingFlow
-
end
-
-
9
autoload_at "action_view/template/error" do
-
9
autoload :MissingTemplate
-
9
autoload :ActionViewError
-
9
autoload :EncodingError
-
9
autoload :TemplateError
-
9
autoload :SyntaxErrorInTemplate
-
9
autoload :WrongEncodingError
-
end
-
end
-
-
9
autoload :CacheExpiry
-
9
autoload :TestCase
-
-
9
def self.eager_load!
-
super
-
ActionView::Helpers.eager_load!
-
ActionView::Template.eager_load!
-
end
-
end
-
-
9
require "active_support/core_ext/string/output_safety"
-
-
9
ActiveSupport.on_load(:i18n) do
-
9
I18n.load_path << File.expand_path("action_view/locale/en.yml", __dir__)
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/module/attr_internal"
-
3
require "active_support/core_ext/module/attribute_accessors"
-
3
require "active_support/ordered_options"
-
3
require "action_view/log_subscriber"
-
3
require "action_view/helpers"
-
3
require "action_view/context"
-
3
require "action_view/template"
-
3
require "action_view/lookup_context"
-
-
3
module ActionView #:nodoc:
-
# = Action View Base
-
#
-
# Action View templates can be written in several ways.
-
# If the template file has a <tt>.erb</tt> extension, then it uses the erubi[https://rubygems.org/gems/erubi]
-
# template system which can embed Ruby into an HTML document.
-
# If the template file has a <tt>.builder</tt> extension, then Jim Weirich's Builder::XmlMarkup library is used.
-
#
-
# == ERB
-
#
-
# You trigger ERB by using embeddings such as <tt><% %></tt>, <tt><% -%></tt>, and <tt><%= %></tt>. The <tt><%= %></tt> tag set is used when you want output. Consider the
-
# following loop for names:
-
#
-
# <b>Names of all the people</b>
-
# <% @people.each do |person| %>
-
# Name: <%= person.name %><br/>
-
# <% end %>
-
#
-
# The loop is set up in regular embedding tags <tt><% %></tt>, and the name is written using the output embedding tag <tt><%= %></tt>. Note that this
-
# is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
-
#
-
# <%# WRONG %>
-
# Hi, Mr. <% puts "Frodo" %>
-
#
-
# If you absolutely must write from within a function use +concat+.
-
#
-
# When on a line that only contains whitespaces except for the tag, <tt><% %></tt> suppresses leading and trailing whitespace,
-
# including the trailing newline. <tt><% %></tt> and <tt><%- -%></tt> are the same.
-
# Note however that <tt><%= %></tt> and <tt><%= -%></tt> are different: only the latter removes trailing whitespaces.
-
#
-
# === Using sub templates
-
#
-
# Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
-
# classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
-
#
-
# <%= render "shared/header" %>
-
# Something really specific and terrific
-
# <%= render "shared/footer" %>
-
#
-
# As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the
-
# result of the rendering. The output embedding writes it to the current template.
-
#
-
# But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance
-
# variables defined using the regular embedding tags. Like this:
-
#
-
# <% @page_title = "A Wonderful Hello" %>
-
# <%= render "shared/header" %>
-
#
-
# Now the header can pick up on the <tt>@page_title</tt> variable and use it for outputting a title tag:
-
#
-
# <title><%= @page_title %></title>
-
#
-
# === Passing local variables to sub templates
-
#
-
# You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
-
#
-
# <%= render "shared/header", { headline: "Welcome", person: person } %>
-
#
-
# These can now be accessed in <tt>shared/header</tt> with:
-
#
-
# Headline: <%= headline %>
-
# First name: <%= person.first_name %>
-
#
-
# The local variables passed to sub templates can be accessed as a hash using the <tt>local_assigns</tt> hash. This lets you access the
-
# variables as:
-
#
-
# Headline: <%= local_assigns[:headline] %>
-
#
-
# This is useful in cases where you aren't sure if the local variable has been assigned. Alternatively, you could also use
-
# <tt>defined? headline</tt> to first check if the variable has been assigned before using it.
-
#
-
# === Template caching
-
#
-
# By default, Rails will compile each template to a method in order to render it. When you alter a template,
-
# Rails will check the file's modification time and recompile it in development mode.
-
#
-
# == Builder
-
#
-
# Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object
-
# named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
-
#
-
# Here are some basic examples:
-
#
-
# xml.em("emphasized") # => <em>emphasized</em>
-
# xml.em { xml.b("emph & bold") } # => <em><b>emph & bold</b></em>
-
# xml.a("A Link", "href" => "http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
-
# xml.target("name" => "compile", "option" => "fast") # => <target option="fast" name="compile"\>
-
# # NOTE: order of attributes is not specified.
-
#
-
# Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
-
#
-
# xml.div do
-
# xml.h1(@person.name)
-
# xml.p(@person.bio)
-
# end
-
#
-
# would produce something like:
-
#
-
# <div>
-
# <h1>David Heinemeier Hansson</h1>
-
# <p>A product of Danish Design during the Winter of '79...</p>
-
# </div>
-
#
-
# Here is a full-length RSS example actually used on Basecamp:
-
#
-
# xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
-
# xml.channel do
-
# xml.title(@feed_title)
-
# xml.link(@url)
-
# xml.description "Basecamp: Recent items"
-
# xml.language "en-us"
-
# xml.ttl "40"
-
#
-
# @recent_items.each do |item|
-
# xml.item do
-
# xml.title(item_title(item))
-
# xml.description(item_description(item)) if item_description(item)
-
# xml.pubDate(item_pubDate(item))
-
# xml.guid(@person.firm.account.url + @recent_items.url(item))
-
# xml.link(@person.firm.account.url + @recent_items.url(item))
-
#
-
# xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
-
# end
-
# end
-
# end
-
# end
-
#
-
# For more information on Builder please consult the {source
-
# code}[https://github.com/jimweirich/builder].
-
3
class Base
-
3
include Helpers, ::ERB::Util, Context
-
-
# Specify the proc used to decorate input tags that refer to attributes with errors.
-
3
cattr_accessor :field_error_proc, default: Proc.new { |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
-
-
# How to complete the streaming when an exception occurs.
-
# This is our best guess: first try to close the attribute, then the tag.
-
3
cattr_accessor :streaming_completion_on_exception, default: %("><script>window.location = "/500.html"</script></html>)
-
-
# Specify whether rendering within namespaced controllers should prefix
-
# the partial paths for ActiveModel objects with the namespace.
-
# (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb)
-
3
cattr_accessor :prefix_partial_path_with_controller_namespace, default: true
-
-
# Specify default_formats that can be rendered.
-
3
cattr_accessor :default_formats
-
-
# Specify whether an error should be raised for missing translations
-
3
cattr_accessor :raise_on_missing_translations, default: false
-
-
# Specify whether submit_tag should automatically disable on click
-
3
cattr_accessor :automatically_disable_submit_tag, default: true
-
-
# Annotate rendered view with file names
-
3
cattr_accessor :annotate_rendered_view_with_filenames, default: false
-
-
3
class_attribute :_routes
-
3
class_attribute :logger
-
-
3
class << self
-
3
delegate :erb_trim_mode=, to: "ActionView::Template::Handlers::ERB"
-
-
3
def cache_template_loading
-
ActionView::Resolver.caching?
-
end
-
-
3
def cache_template_loading=(value)
-
ActionView::Resolver.caching = value
-
end
-
-
3
def xss_safe? #:nodoc:
-
true
-
end
-
-
3
def with_empty_template_cache # :nodoc:
-
subclass = Class.new(self) {
-
# We can't implement these as self.class because subclasses will
-
# share the same template cache as superclasses, so "changed?" won't work
-
# correctly.
-
define_method(:compiled_method_container) { subclass }
-
define_singleton_method(:compiled_method_container) { subclass }
-
-
def self.name
-
superclass.name
-
end
-
-
def inspect
-
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
-
end
-
}
-
end
-
-
3
def changed?(other) # :nodoc:
-
compiled_method_container != other.compiled_method_container
-
end
-
end
-
-
3
attr_reader :view_renderer, :lookup_context
-
3
attr_internal :config, :assigns
-
-
3
delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context
-
-
3
def assign(new_assigns) # :nodoc:
-
@_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
-
end
-
-
# :stopdoc:
-
-
3
def self.build_lookup_context(context)
-
case context
-
when ActionView::Renderer
-
context.lookup_context
-
when Array
-
ActionView::LookupContext.new(context)
-
when ActionView::PathSet
-
ActionView::LookupContext.new(context)
-
when nil
-
ActionView::LookupContext.new([])
-
else
-
raise NotImplementedError, context.class.name
-
end
-
end
-
-
3
def self.empty
-
with_view_paths([])
-
end
-
-
3
def self.with_view_paths(view_paths, assigns = {}, controller = nil)
-
with_context ActionView::LookupContext.new(view_paths), assigns, controller
-
end
-
-
3
def self.with_context(context, assigns = {}, controller = nil)
-
new context, assigns, controller
-
end
-
-
3
NULL = Object.new
-
-
# :startdoc:
-
-
3
def initialize(lookup_context = nil, assigns = {}, controller = nil, formats = NULL) #:nodoc:
-
@_config = ActiveSupport::InheritableOptions.new
-
-
unless formats == NULL
-
ActiveSupport::Deprecation.warn <<~eowarn.squish
-
Passing formats to ActionView::Base.new is deprecated
-
eowarn
-
end
-
-
case lookup_context
-
when ActionView::LookupContext
-
@lookup_context = lookup_context
-
else
-
ActiveSupport::Deprecation.warn <<~eowarn.squish
-
ActionView::Base instances should be constructed with a lookup context,
-
assignments, and a controller.
-
eowarn
-
@lookup_context = self.class.build_lookup_context(lookup_context)
-
end
-
-
@view_renderer = ActionView::Renderer.new @lookup_context
-
@current_template = nil
-
-
@cache_hit = {}
-
assign(assigns)
-
assign_controller(controller)
-
_prepare_context
-
end
-
-
3
def _run(method, template, locals, buffer, add_to_stack: true, &block)
-
_old_output_buffer, _old_template = @output_buffer, @current_template
-
@current_template = template if add_to_stack
-
@output_buffer = buffer
-
send(method, locals, buffer, &block)
-
ensure
-
@output_buffer, @current_template = _old_output_buffer, _old_template
-
end
-
-
3
def compiled_method_container
-
if self.class == ActionView::Base
-
ActiveSupport::Deprecation.warn <<~eowarn.squish
-
ActionView::Base instances must implement `compiled_method_container`
-
or use the class method `with_empty_template_cache` for constructing
-
an ActionView::Base instance that has an empty cache.
-
eowarn
-
end
-
-
self.class
-
end
-
-
3
def in_rendering_context(options)
-
old_view_renderer = @view_renderer
-
old_lookup_context = @lookup_context
-
-
if !lookup_context.html_fallback_for_js && options[:formats]
-
formats = Array(options[:formats])
-
if formats == [:js]
-
formats << :html
-
end
-
@lookup_context = lookup_context.with_prepended_formats(formats)
-
@view_renderer = ActionView::Renderer.new @lookup_context
-
end
-
-
yield @view_renderer
-
ensure
-
@view_renderer = old_view_renderer
-
@lookup_context = old_lookup_context
-
end
-
-
3
ActiveSupport.run_load_hooks(:action_view, self)
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/string/output_safety"
-
-
module ActionView
-
# Used as a buffer for views
-
#
-
# The main difference between this and ActiveSupport::SafeBuffer
-
# is for the methods `<<` and `safe_expr_append=` the inputs are
-
# checked for nil before they are assigned and `to_s` is called on
-
# the input. For example:
-
#
-
# obuf = ActionView::OutputBuffer.new "hello"
-
# obuf << 5
-
# puts obuf # => "hello5"
-
#
-
# sbuf = ActiveSupport::SafeBuffer.new "hello"
-
# sbuf << 5
-
# puts sbuf # => "hello\u0005"
-
#
-
class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
-
def initialize(*)
-
super
-
encode!
-
end
-
-
def <<(value)
-
return self if value.nil?
-
super(value.to_s)
-
end
-
alias :append= :<<
-
-
def safe_expr_append=(val)
-
return self if val.nil?
-
safe_concat val.to_s
-
end
-
-
alias :safe_append= :safe_concat
-
end
-
-
class StreamingBuffer #:nodoc:
-
def initialize(block)
-
@block = block
-
end
-
-
def <<(value)
-
value = value.to_s
-
value = ERB::Util.h(value) unless value.html_safe?
-
@block.call(value)
-
end
-
alias :concat :<<
-
alias :append= :<<
-
-
def safe_concat(value)
-
@block.call(value.to_s)
-
end
-
alias :safe_append= :safe_concat
-
-
def html_safe?
-
true
-
end
-
-
def html_safe
-
self
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
class CacheExpiry
-
class Executor
-
def initialize(watcher:)
-
@cache_expiry = CacheExpiry.new(watcher: watcher)
-
end
-
-
def before(target)
-
@cache_expiry.clear_cache_if_necessary
-
end
-
end
-
-
def initialize(watcher:)
-
@watched_dirs = nil
-
@watcher_class = watcher
-
@watcher = nil
-
@mutex = Mutex.new
-
end
-
-
def clear_cache_if_necessary
-
@mutex.synchronize do
-
watched_dirs = dirs_to_watch
-
return if watched_dirs.empty?
-
-
if watched_dirs != @watched_dirs
-
@watched_dirs = watched_dirs
-
@watcher = @watcher_class.new([], watched_dirs) do
-
clear_cache
-
end
-
@watcher.execute
-
else
-
@watcher.execute_if_updated
-
end
-
end
-
end
-
-
def clear_cache
-
ActionView::LookupContext::DetailsKey.clear
-
end
-
-
private
-
def dirs_to_watch
-
all_view_paths.grep(FileSystemResolver).map!(&:path).tap(&:uniq!).sort!
-
end
-
-
def all_view_paths
-
ActionView::ViewPaths.all_view_paths.flat_map(&:paths)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
6
module ActionView
-
# = Action View Context
-
#
-
# Action View contexts are supplied to Action Controller to render a template.
-
# The default Action View context is ActionView::Base.
-
#
-
# In order to work with Action Controller, a Context must just include this
-
# module. The initialization of the variables used by the context
-
# (@output_buffer, @view_flow, and @virtual_path) is responsibility of the
-
# object that includes this module (although you can call _prepare_context
-
# defined below).
-
6
module Context
-
6
attr_accessor :output_buffer, :view_flow
-
-
# Prepares the context by setting the appropriate instance variables.
-
6
def _prepare_context
-
@view_flow = OutputFlow.new
-
@output_buffer = nil
-
end
-
-
# Encapsulates the interaction with the view flow so it
-
# returns the correct buffer on +yield+. This is usually
-
# overwritten by helpers to add more behavior.
-
6
def _layout_for(name = nil)
-
name ||= :layout
-
view_flow.get(name).html_safe
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "concurrent/map"
-
3
require "action_view/path_set"
-
-
3
module ActionView
-
3
class DependencyTracker # :nodoc:
-
3
@trackers = Concurrent::Map.new
-
-
3
def self.find_dependencies(name, template, view_paths = nil)
-
tracker = @trackers[template.handler]
-
return [] unless tracker
-
-
tracker.call(name, template, view_paths)
-
end
-
-
3
def self.register_tracker(extension, tracker)
-
3
handler = Template.handler_for_extension(extension)
-
3
if tracker.respond_to?(:supports_view_paths?)
-
3
@trackers[handler] = tracker
-
else
-
@trackers[handler] = lambda { |name, template, _|
-
tracker.call(name, template)
-
}
-
end
-
end
-
-
3
def self.remove_tracker(handler)
-
@trackers.delete(handler)
-
end
-
-
3
class ERBTracker # :nodoc:
-
3
EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/
-
-
# A valid ruby identifier - suitable for class, method and specially variable names
-
3
IDENTIFIER = /
-
[[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore
-
[[:word:]]* # followed by optional letters, numbers or underscores
-
/x
-
-
# Any kind of variable name. e.g. @instance, @@class, $global or local.
-
# Possibly following a method call chain
-
3
VARIABLE_OR_METHOD_CHAIN = /
-
(?:\$|@{1,2})? # optional global, instance or class variable indicator
-
(?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls
-
(?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC
-
/x
-
-
# A simple string literal. e.g. "School's out!"
-
3
STRING = /
-
(?<quote>['"]) # an opening quote
-
(?<static>.*?) # with anything inside, captured as STATIC
-
\k<quote> # and a matching closing quote
-
/x
-
-
# Part of any hash containing the :partial key
-
3
PARTIAL_HASH_KEY = /
-
(?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax
-
\s* # followed by optional spaces
-
/x
-
-
# Part of any hash containing the :layout key
-
3
LAYOUT_HASH_KEY = /
-
(?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax
-
\s* # followed by optional spaces
-
/x
-
-
# Matches:
-
# partial: "comments/comment", collection: @all_comments => "comments/comment"
-
# (object: @single_comment, partial: "comments/comment") => "comments/comment"
-
#
-
# "comments/comments"
-
# 'comments/comments'
-
# ('comments/comments')
-
#
-
# (@topic) => "topics/topic"
-
# topics => "topics/topic"
-
# (message.topics) => "topics/topic"
-
3
RENDER_ARGUMENTS = /\A
-
(?:\s*\(?\s*) # optional opening paren surrounded by spaces
-
(?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration
-
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
-
/xm
-
-
3
LAYOUT_DEPENDENCY = /\A
-
(?:\s*\(?\s*) # optional opening paren surrounded by spaces
-
(?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
-
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
-
/xm
-
-
3
def self.supports_view_paths? # :nodoc:
-
true
-
end
-
-
3
def self.call(name, template, view_paths = nil)
-
new(name, template, view_paths).dependencies
-
end
-
-
3
def initialize(name, template, view_paths = nil)
-
48
@name, @template, @view_paths = name, template, view_paths
-
end
-
-
3
def dependencies
-
48
render_dependencies + explicit_dependencies
-
end
-
-
3
attr_reader :name, :template
-
3
private :name, :template
-
-
3
private
-
3
def source
-
96
template.source
-
end
-
-
3
def directory
-
12
name.split("/")[0..-2].join("/")
-
end
-
-
3
def render_dependencies
-
48
render_dependencies = []
-
48
render_calls = source.split(/\brender\b/).drop(1)
-
-
48
render_calls.each do |arguments|
-
69
add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
-
69
add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
-
end
-
-
48
render_dependencies.uniq
-
end
-
-
3
def add_dependencies(render_dependencies, arguments, pattern)
-
138
arguments.scan(pattern) do
-
75
match = Regexp.last_match
-
75
add_dynamic_dependency(render_dependencies, match[:dynamic])
-
75
add_static_dependency(render_dependencies, match[:static], match[:quote])
-
end
-
end
-
-
3
def add_dynamic_dependency(dependencies, dependency)
-
75
if dependency
-
27
dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
-
end
-
end
-
-
3
def add_static_dependency(dependencies, dependency, quote_type)
-
75
if quote_type == '"'
-
# Ignore if there is interpolation
-
15
return if dependency.include?('#{')
-
end
-
-
72
if dependency
-
45
if dependency.include?("/")
-
33
dependencies << dependency
-
else
-
12
dependencies << "#{directory}/#{dependency}"
-
end
-
end
-
end
-
-
3
def resolve_directories(wildcard_dependencies)
-
48
return [] unless @view_paths
-
-
wildcard_dependencies.flat_map { |query, templates|
-
@view_paths.find_all_with_query(query).map do |template|
-
"#{File.dirname(query)}/#{File.basename(template).split('.').first}"
-
end
-
}.sort
-
end
-
-
3
def explicit_dependencies
-
48
dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
-
-
48
wildcards, explicits = dependencies.partition { |dependency| dependency.end_with?("*") }
-
-
48
(explicits + resolve_directories(wildcards)).uniq
-
end
-
end
-
-
3
register_tracker :erb, ERBTracker
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "action_view/dependency_tracker"
-
-
3
module ActionView
-
3
class Digestor
-
3
@@digest_mutex = Mutex.new
-
-
3
class << self
-
# Supported options:
-
#
-
# * <tt>name</tt> - Template name
-
# * <tt>format</tt> - Template format
-
# * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
-
# * <tt>dependencies</tt> - An array of dependent views
-
3
def digest(name:, format: nil, finder:, dependencies: nil)
-
if dependencies.nil? || dependencies.empty?
-
cache_key = "#{name}.#{format}"
-
else
-
cache_key = [ name, format, dependencies ].flatten.compact.join(".")
-
end
-
-
# this is a correctly done double-checked locking idiom
-
# (Concurrent::Map's lookups have volatile semantics)
-
finder.digest_cache[cache_key] || @@digest_mutex.synchronize do
-
finder.digest_cache.fetch(cache_key) do # re-check under lock
-
partial = name.include?("/_")
-
root = tree(name, finder, partial)
-
dependencies.each do |injected_dep|
-
root.children << Injected.new(injected_dep, nil, nil)
-
end if dependencies
-
finder.digest_cache[cache_key] = root.digest(finder)
-
end
-
end
-
end
-
-
3
def logger
-
ActionView::Base.logger || NullLogger
-
end
-
-
# Create a dependency tree for template named +name+.
-
3
def tree(name, finder, partial = false, seen = {})
-
logical_name = name.gsub(%r|/_|, "/")
-
interpolated = name.include?("#")
-
-
if !interpolated && (template = find_template(finder, logical_name, [], partial, []))
-
if node = seen[template.identifier] # handle cycles in the tree
-
node
-
else
-
node = seen[template.identifier] = Node.create(name, logical_name, template, partial)
-
-
deps = DependencyTracker.find_dependencies(name, template, finder.view_paths)
-
deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file|
-
node.children << tree(dep_file, finder, true, seen)
-
end
-
node
-
end
-
else
-
unless interpolated # Dynamic template partial names can never be tracked
-
logger.error " Couldn't find template for digesting: #{name}"
-
end
-
-
seen[name] ||= Missing.new(name, logical_name, nil)
-
end
-
end
-
-
3
private
-
3
def find_template(finder, name, prefixes, partial, keys)
-
finder.disable_cache do
-
finder.find_all(name, prefixes, partial, keys).first
-
end
-
end
-
end
-
-
3
class Node
-
3
attr_reader :name, :logical_name, :template, :children
-
-
3
def self.create(name, logical_name, template, partial)
-
klass = partial ? Partial : Node
-
klass.new(name, logical_name, template, [])
-
end
-
-
3
def initialize(name, logical_name, template, children = [])
-
@name = name
-
@logical_name = logical_name
-
@template = template
-
@children = children
-
end
-
-
3
def digest(finder, stack = [])
-
ActiveSupport::Digest.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
-
end
-
-
3
def dependency_digest(finder, stack)
-
children.map do |node|
-
if stack.include?(node)
-
false
-
else
-
finder.digest_cache[node.name] ||= begin
-
stack.push node
-
node.digest(finder, stack).tap { stack.pop }
-
end
-
end
-
end.join("-")
-
end
-
-
3
def to_dep_map
-
children.any? ? { name => children.map(&:to_dep_map) } : name
-
end
-
end
-
-
3
class Partial < Node; end
-
-
3
class Missing < Node
-
3
def digest(finder, _ = []) "" end
-
end
-
-
3
class Injected < Node
-
3
def digest(finder, _ = []) name end
-
end
-
-
3
class NullLogger
-
3
def self.debug(_); end
-
3
def self.error(_); end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/string/output_safety"
-
-
module ActionView
-
class OutputFlow #:nodoc:
-
attr_reader :content
-
-
def initialize
-
@content = Hash.new { |h, k| h[k] = ActiveSupport::SafeBuffer.new }
-
end
-
-
# Called by _layout_for to read stored values.
-
def get(key)
-
@content[key]
-
end
-
-
# Called by each renderer object to set the layout contents.
-
def set(key, value)
-
@content[key] = ActiveSupport::SafeBuffer.new(value)
-
end
-
-
# Called by content_for
-
def append(key, value)
-
@content[key] << value
-
end
-
alias_method :append!, :append
-
end
-
-
class StreamingFlow < OutputFlow #:nodoc:
-
def initialize(view, fiber)
-
@view = view
-
@parent = nil
-
@child = view.output_buffer
-
@content = view.view_flow.content
-
@fiber = fiber
-
@root = Fiber.current.object_id
-
end
-
-
# Try to get stored content. If the content
-
# is not available and we're inside the layout fiber,
-
# then it will begin waiting for the given key and yield.
-
def get(key)
-
return super if @content.key?(key)
-
-
if inside_fiber?
-
view = @view
-
-
begin
-
@waiting_for = key
-
view.output_buffer, @parent = @child, view.output_buffer
-
Fiber.yield
-
ensure
-
@waiting_for = nil
-
view.output_buffer, @child = @parent, view.output_buffer
-
end
-
end
-
-
super
-
end
-
-
# Appends the contents for the given key. This is called
-
# by providing and resuming back to the fiber,
-
# if that's the key it's waiting for.
-
def append!(key, value)
-
super
-
@fiber.resume if @waiting_for == key
-
end
-
-
private
-
def inside_fiber?
-
Fiber.current.object_id != @root
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
# Returns the version of the currently loaded Action View as a <tt>Gem::Version</tt>
-
9
def self.gem_version
-
Gem::Version.new VERSION::STRING
-
end
-
-
9
module VERSION
-
9
MAJOR = 6
-
9
MINOR = 1
-
9
TINY = 0
-
9
PRE = "alpha"
-
-
9
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "active_support/benchmarkable"
-
-
9
module ActionView #:nodoc:
-
9
module Helpers #:nodoc:
-
9
extend ActiveSupport::Autoload
-
-
9
autoload :ActiveModelHelper
-
9
autoload :AssetTagHelper
-
9
autoload :AssetUrlHelper
-
9
autoload :AtomFeedHelper
-
9
autoload :CacheHelper
-
9
autoload :CaptureHelper
-
9
autoload :ControllerHelper
-
9
autoload :CspHelper
-
9
autoload :CsrfHelper
-
9
autoload :DateHelper
-
9
autoload :DebugHelper
-
9
autoload :FormHelper
-
9
autoload :FormOptionsHelper
-
9
autoload :FormTagHelper
-
9
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
-
9
autoload :NumberHelper
-
9
autoload :OutputSafetyHelper
-
9
autoload :RenderingHelper
-
9
autoload :SanitizeHelper
-
9
autoload :TagHelper
-
9
autoload :TextHelper
-
9
autoload :TranslationHelper
-
9
autoload :UrlHelper
-
9
autoload :Tags
-
-
9
def self.eager_load!
-
super
-
Tags.eager_load!
-
end
-
-
9
extend ActiveSupport::Concern
-
-
9
include ActiveSupport::Benchmarkable
-
9
include ActiveModelHelper
-
9
include AssetTagHelper
-
9
include AssetUrlHelper
-
9
include AtomFeedHelper
-
9
include CacheHelper
-
9
include CaptureHelper
-
9
include ControllerHelper
-
9
include CspHelper
-
9
include CsrfHelper
-
9
include DateHelper
-
9
include DebugHelper
-
9
include FormHelper
-
9
include FormOptionsHelper
-
9
include FormTagHelper
-
9
include JavaScriptHelper
-
9
include NumberHelper
-
9
include OutputSafetyHelper
-
9
include RenderingHelper
-
9
include SanitizeHelper
-
9
include TagHelper
-
9
include TextHelper
-
9
include TranslationHelper
-
9
include UrlHelper
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "active_support/core_ext/module/attribute_accessors"
-
9
require "active_support/core_ext/enumerable"
-
-
9
module ActionView
-
# = Active Model Helpers
-
9
module Helpers #:nodoc:
-
9
module ActiveModelHelper
-
end
-
-
9
module ActiveModelInstanceTag
-
9
def object
-
@active_model_object ||= begin
-
object = super
-
object.respond_to?(:to_model) ? object.to_model : object
-
end
-
end
-
-
9
def content_tag(type, options, *)
-
select_markup_helper?(type) ? super : error_wrapping(super)
-
end
-
-
9
def tag(type, options, *)
-
tag_generate_errors?(options) ? error_wrapping(super) : super
-
end
-
-
9
def error_wrapping(html_tag)
-
if object_has_errors?
-
Base.field_error_proc.call(html_tag, self)
-
else
-
html_tag
-
end
-
end
-
-
9
def error_message
-
object.errors[@method_name]
-
end
-
-
9
private
-
9
def object_has_errors?
-
object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
-
end
-
-
9
def select_markup_helper?(type)
-
["optgroup", "option"].include?(type)
-
end
-
-
9
def tag_generate_errors?(options)
-
options["type"] != "hidden"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "active_support/core_ext/array/extract_options"
-
9
require "active_support/core_ext/hash/keys"
-
9
require "active_support/core_ext/object/inclusion"
-
9
require "action_view/helpers/asset_url_helper"
-
9
require "action_view/helpers/tag_helper"
-
-
9
module ActionView
-
# = Action View Asset Tag Helpers
-
9
module Helpers #:nodoc:
-
# This module provides methods for generating HTML that links views to assets such
-
# as images, JavaScripts, stylesheets, and feeds. These methods do not verify
-
# the assets exist before linking to them:
-
#
-
# image_tag("rails.png")
-
# # => <img src="/assets/rails.png" />
-
# stylesheet_link_tag("application")
-
# # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
-
9
module AssetTagHelper
-
9
extend ActiveSupport::Concern
-
-
9
include AssetUrlHelper
-
9
include TagHelper
-
-
# Returns an HTML script tag for each of the +sources+ provided.
-
#
-
# Sources may be paths to JavaScript files. Relative paths are assumed to be relative
-
# to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document
-
# root. Relative paths are idiomatic, use absolute paths only when needed.
-
#
-
# When passing paths, the ".js" extension is optional. If you do not want ".js"
-
# appended to the path <tt>extname: false</tt> can be set on the options.
-
#
-
# You can modify the HTML attributes of the script tag by passing a hash as the
-
# last argument.
-
#
-
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
-
# source, and include other JavaScript or CoffeeScript files inside the manifest.
-
#
-
# If the server supports Early Hints header links for these assets will be
-
# automatically pushed.
-
#
-
# ==== Options
-
#
-
# When the last parameter is a hash you can add HTML attributes using that
-
# parameter. The following options are supported:
-
#
-
# * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
-
# already exists. This only applies for relative URLs.
-
# * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only
-
# applies when a relative URL and +host+ options are provided.
-
# * <tt>:host</tt> - When a relative URL is provided the host is added to the
-
# that path.
-
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
-
# when it is set to true.
-
# * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
-
# you have Content Security Policy enabled.
-
#
-
# ==== Examples
-
#
-
# javascript_include_tag "xmlhr"
-
# # => <script src="/assets/xmlhr.debug-1284139606.js"></script>
-
#
-
# javascript_include_tag "xmlhr", host: "localhost", protocol: "https"
-
# # => <script src="https://localhost/assets/xmlhr.debug-1284139606.js"></script>
-
#
-
# javascript_include_tag "template.jst", extname: false
-
# # => <script src="/assets/template.debug-1284139606.jst"></script>
-
#
-
# javascript_include_tag "xmlhr.js"
-
# # => <script src="/assets/xmlhr.debug-1284139606.js"></script>
-
#
-
# javascript_include_tag "common.javascript", "/elsewhere/cools"
-
# # => <script src="/assets/common.javascript.debug-1284139606.js"></script>
-
# # <script src="/elsewhere/cools.debug-1284139606.js"></script>
-
#
-
# javascript_include_tag "http://www.example.com/xmlhr"
-
# # => <script src="http://www.example.com/xmlhr"></script>
-
#
-
# javascript_include_tag "http://www.example.com/xmlhr.js"
-
# # => <script src="http://www.example.com/xmlhr.js"></script>
-
#
-
# javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true
-
# # => <script src="http://www.example.com/xmlhr.js" nonce="..."></script>
-
9
def javascript_include_tag(*sources)
-
options = sources.extract_options!.stringify_keys
-
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
-
preload_links = []
-
-
sources_tags = sources.uniq.map { |source|
-
href = path_to_javascript(source, path_options)
-
preload_links << "<#{href}>; rel=preload; as=script"
-
tag_options = {
-
"src" => href
-
}.merge!(options)
-
if tag_options["nonce"] == true
-
tag_options["nonce"] = content_security_policy_nonce
-
end
-
content_tag("script", "", tag_options)
-
}.join("\n").html_safe
-
-
send_preload_links_header(preload_links)
-
-
sources_tags
-
end
-
-
# Returns a stylesheet link tag for the sources specified as arguments. If
-
# you don't specify an extension, <tt>.css</tt> will be appended automatically.
-
# You can modify the link attributes by passing a hash as the last argument.
-
# For historical reasons, the 'media' attribute will always be present and defaults
-
# to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
-
# apply to all media types.
-
#
-
# If the server supports Early Hints header links for these assets will be
-
# automatically pushed.
-
#
-
# stylesheet_link_tag "style"
-
# # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
-
#
-
# stylesheet_link_tag "style.css"
-
# # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
-
#
-
# stylesheet_link_tag "http://www.example.com/style.css"
-
# # => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
-
#
-
# stylesheet_link_tag "style", media: "all"
-
# # => <link href="/assets/style.css" media="all" rel="stylesheet" />
-
#
-
# stylesheet_link_tag "style", media: "print"
-
# # => <link href="/assets/style.css" media="print" rel="stylesheet" />
-
#
-
# stylesheet_link_tag "random.styles", "/css/stylish"
-
# # => <link href="/assets/random.styles" media="screen" rel="stylesheet" />
-
# # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
-
9
def stylesheet_link_tag(*sources)
-
options = sources.extract_options!.stringify_keys
-
path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
-
preload_links = []
-
-
sources_tags = sources.uniq.map { |source|
-
href = path_to_stylesheet(source, path_options)
-
preload_links << "<#{href}>; rel=preload; as=style"
-
tag_options = {
-
"rel" => "stylesheet",
-
"media" => "screen",
-
"href" => href
-
}.merge!(options)
-
tag(:link, tag_options)
-
}.join("\n").html_safe
-
-
send_preload_links_header(preload_links)
-
-
sources_tags
-
end
-
-
# Returns a link tag that browsers and feed readers can use to auto-detect
-
# an RSS, Atom, or JSON feed. The +type+ can be <tt>:rss</tt> (default),
-
# <tt>:atom</tt>, or <tt>:json</tt>. Control the link options in url_for format
-
# using the +url_options+. You can modify the LINK tag itself in +tag_options+.
-
#
-
# ==== Options
-
#
-
# * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
-
# * <tt>:type</tt> - Override the auto-generated mime type
-
# * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
-
#
-
# ==== Examples
-
#
-
# auto_discovery_link_tag
-
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/action" />
-
# auto_discovery_link_tag(:atom)
-
# # => <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.currenthost.com/controller/action" />
-
# auto_discovery_link_tag(:json)
-
# # => <link rel="alternate" type="application/json" title="JSON" href="http://www.currenthost.com/controller/action" />
-
# auto_discovery_link_tag(:rss, {action: "feed"})
-
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/controller/feed" />
-
# auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"})
-
# # => <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.currenthost.com/controller/feed" />
-
# auto_discovery_link_tag(:rss, {controller: "news", action: "feed"})
-
# # => <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.currenthost.com/news/feed" />
-
# auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"})
-
# # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed.rss" />
-
9
def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
-
if !(type == :rss || type == :atom || type == :json) && tag_options[:type].blank?
-
raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss, :atom, or :json.")
-
end
-
-
tag(
-
"link",
-
"rel" => tag_options[:rel] || "alternate",
-
"type" => tag_options[:type] || Template::Types[type].to_s,
-
"title" => tag_options[:title] || type.to_s.upcase,
-
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(only_path: false)) : url_options
-
)
-
end
-
-
# Returns a link tag for a favicon managed by the asset pipeline.
-
#
-
# If a page has no link like the one generated by this helper, browsers
-
# ask for <tt>/favicon.ico</tt> automatically, and cache the file if the
-
# request succeeds. If the favicon changes it is hard to get it updated.
-
#
-
# To have better control applications may let the asset pipeline manage
-
# their favicon storing the file under <tt>app/assets/images</tt>, and
-
# using this helper to generate its corresponding link tag.
-
#
-
# The helper gets the name of the favicon file as first argument, which
-
# defaults to "favicon.ico", and also supports +:rel+ and +:type+ options
-
# to override their defaults, "shortcut icon" and "image/x-icon"
-
# respectively:
-
#
-
# favicon_link_tag
-
# # => <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon" />
-
#
-
# favicon_link_tag 'myicon.ico'
-
# # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/x-icon" />
-
#
-
# Mobile Safari looks for a different link tag, pointing to an image that
-
# will be used if you add the page to the home screen of an iOS device.
-
# The following call would generate such a tag:
-
#
-
# favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
-
# # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
-
9
def favicon_link_tag(source = "favicon.ico", options = {})
-
tag("link", {
-
rel: "shortcut icon",
-
type: "image/x-icon",
-
href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
-
}.merge!(options.symbolize_keys))
-
end
-
-
# Returns a link tag that browsers can use to preload the +source+.
-
# The +source+ can be the path of a resource managed by asset pipeline,
-
# a full path, or an URI.
-
#
-
# ==== Options
-
#
-
# * <tt>:type</tt> - Override the auto-generated mime type, defaults to the mime type for +source+ extension.
-
# * <tt>:as</tt> - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type.
-
# * <tt>:crossorigin</tt> - Specify the crossorigin attribute, required to load cross-origin resources.
-
# * <tt>:nopush</tt> - Specify if the use of server push is not desired for the resource. Defaults to +false+.
-
#
-
# ==== Examples
-
#
-
# preload_link_tag("custom_theme.css")
-
# # => <link rel="preload" href="/assets/custom_theme.css" as="style" type="text/css" />
-
#
-
# preload_link_tag("/videos/video.webm")
-
# # => <link rel="preload" href="/videos/video.mp4" as="video" type="video/webm" />
-
#
-
# preload_link_tag(post_path(format: :json), as: "fetch")
-
# # => <link rel="preload" href="/posts.json" as="fetch" type="application/json" />
-
#
-
# preload_link_tag("worker.js", as: "worker")
-
# # => <link rel="preload" href="/assets/worker.js" as="worker" type="text/javascript" />
-
#
-
# preload_link_tag("//example.com/font.woff2")
-
# # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous"/>
-
#
-
# preload_link_tag("//example.com/font.woff2", crossorigin: "use-credentials")
-
# # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="use-credentials" />
-
#
-
# preload_link_tag("/media/audio.ogg", nopush: true)
-
# # => <link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />
-
#
-
9
def preload_link_tag(source, options = {})
-
href = asset_path(source, skip_pipeline: options.delete(:skip_pipeline))
-
extname = File.extname(source).downcase.delete(".")
-
mime_type = options.delete(:type) || Template::Types[extname]&.to_s
-
as_type = options.delete(:as) || resolve_link_as(extname, mime_type)
-
crossorigin = options.delete(:crossorigin)
-
crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
-
nopush = options.delete(:nopush) || false
-
-
link_tag = tag.link(**{
-
rel: "preload",
-
href: href,
-
as: as_type,
-
type: mime_type,
-
crossorigin: crossorigin
-
}.merge!(options.symbolize_keys))
-
-
preload_link = "<#{href}>; rel=preload; as=#{as_type}"
-
preload_link += "; type=#{mime_type}" if mime_type
-
preload_link += "; crossorigin=#{crossorigin}" if crossorigin
-
preload_link += "; nopush" if nopush
-
-
send_preload_links_header([preload_link])
-
-
link_tag
-
end
-
-
# Returns an HTML image tag for the +source+. The +source+ can be a full
-
# path, a file, or an Active Storage attachment.
-
#
-
# ==== Options
-
#
-
# You can add HTML attributes using the +options+. The +options+ supports
-
# additional keys for convenience and conformance:
-
#
-
# * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
-
# width="30" and height="45", and "50" becomes width="50" and height="50".
-
# <tt>:size</tt> will be ignored if the value is not in the correct format.
-
# * <tt>:srcset</tt> - If supplied as a hash or array of <tt>[source, descriptor]</tt>
-
# pairs, each image path will be expanded before the list is formatted as a string.
-
#
-
# ==== Examples
-
#
-
# Assets (images that are part of your app):
-
#
-
# image_tag("icon")
-
# # => <img src="/assets/icon" />
-
# image_tag("icon.png")
-
# # => <img src="/assets/icon.png" />
-
# image_tag("icon.png", size: "16x10", alt: "Edit Entry")
-
# # => <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
-
# image_tag("/icons/icon.gif", size: "16")
-
# # => <img src="/icons/icon.gif" width="16" height="16" />
-
# image_tag("/icons/icon.gif", height: '32', width: '32')
-
# # => <img height="32" src="/icons/icon.gif" width="32" />
-
# image_tag("/icons/icon.gif", class: "menu_icon")
-
# # => <img class="menu_icon" src="/icons/icon.gif" />
-
# image_tag("/icons/icon.gif", data: { title: 'Rails Application' })
-
# # => <img data-title="Rails Application" src="/icons/icon.gif" />
-
# image_tag("icon.png", srcset: { "icon_2x.png" => "2x", "icon_4x.png" => "4x" })
-
# # => <img src="/assets/icon.png" srcset="/assets/icon_2x.png 2x, /assets/icon_4x.png 4x">
-
# image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
-
# # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
-
#
-
# Active Storage blobs (images that are uploaded by the users of your app):
-
#
-
# image_tag(user.avatar)
-
# # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
-
# image_tag(user.avatar.variant(resize_to_limit: [100, 100]))
-
# # => <img src="/rails/active_storage/representations/.../tiger.jpg" />
-
# image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100')
-
# # => <img width="100" height="100" src="/rails/active_storage/representations/.../tiger.jpg" />
-
9
def image_tag(source, options = {})
-
options = options.symbolize_keys
-
check_for_image_tag_errors(options)
-
skip_pipeline = options.delete(:skip_pipeline)
-
-
options[:src] = resolve_image_source(source, skip_pipeline)
-
-
if options[:srcset] && !options[:srcset].is_a?(String)
-
options[:srcset] = options[:srcset].map do |src_path, size|
-
src_path = path_to_image(src_path, skip_pipeline: skip_pipeline)
-
"#{src_path} #{size}"
-
end.join(", ")
-
end
-
-
options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
-
tag("img", options)
-
end
-
-
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
-
# a single video tag will be returned. If +sources+ is an array, a video
-
# tag with nested source tags for each source will be returned. The
-
# +sources+ can be full paths or files that exist in your public videos
-
# directory.
-
#
-
# ==== Options
-
#
-
# When the last parameter is a hash you can add HTML attributes using that
-
# parameter. The following options are supported:
-
#
-
# * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
-
# before the video loads. The path is calculated like the +src+ of +image_tag+.
-
# * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
-
# width="30" and height="45", and "50" becomes width="50" and height="50".
-
# <tt>:size</tt> will be ignored if the value is not in the correct format.
-
# * <tt>:poster_skip_pipeline</tt> will bypass the asset pipeline when using
-
# the <tt>:poster</tt> option instead using an asset in the public folder.
-
#
-
# ==== Examples
-
#
-
# video_tag("trailer")
-
# # => <video src="/videos/trailer"></video>
-
# video_tag("trailer.ogg")
-
# # => <video src="/videos/trailer.ogg"></video>
-
# video_tag("trailer.ogg", controls: true, preload: 'none')
-
# # => <video preload="none" controls="controls" src="/videos/trailer.ogg"></video>
-
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
-
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
-
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true)
-
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="screenshot.png"></video>
-
# video_tag("/trailers/hd.avi", size: "16x16")
-
# # => <video src="/trailers/hd.avi" width="16" height="16"></video>
-
# video_tag("/trailers/hd.avi", size: "16")
-
# # => <video height="16" src="/trailers/hd.avi" width="16"></video>
-
# video_tag("/trailers/hd.avi", height: '32', width: '32')
-
# # => <video height="32" src="/trailers/hd.avi" width="32"></video>
-
# video_tag("trailer.ogg", "trailer.flv")
-
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
-
# video_tag(["trailer.ogg", "trailer.flv"])
-
# # => <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
-
# video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
-
# # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
-
9
def video_tag(*sources)
-
options = sources.extract_options!.symbolize_keys
-
public_poster_folder = options.delete(:poster_skip_pipeline)
-
sources << options
-
multiple_sources_tag_builder("video", sources) do |tag_options|
-
tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster]
-
tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size]
-
end
-
end
-
-
# Returns an HTML audio tag for the +sources+. If +sources+ is a string,
-
# a single audio tag will be returned. If +sources+ is an array, an audio
-
# tag with nested source tags for each source will be returned. The
-
# +sources+ can be full paths or files that exist in your public audios
-
# directory.
-
#
-
# When the last parameter is a hash you can add HTML attributes using that
-
# parameter.
-
#
-
# audio_tag("sound")
-
# # => <audio src="/audios/sound"></audio>
-
# audio_tag("sound.wav")
-
# # => <audio src="/audios/sound.wav"></audio>
-
# audio_tag("sound.wav", autoplay: true, controls: true)
-
# # => <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav"></audio>
-
# audio_tag("sound.wav", "sound.mid")
-
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
-
9
def audio_tag(*sources)
-
multiple_sources_tag_builder("audio", sources)
-
end
-
-
9
private
-
9
def multiple_sources_tag_builder(type, sources)
-
options = sources.extract_options!.symbolize_keys
-
skip_pipeline = options.delete(:skip_pipeline)
-
sources.flatten!
-
-
yield options if block_given?
-
-
if sources.size > 1
-
content_tag(type, options) do
-
safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) }
-
end
-
else
-
options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline)
-
content_tag(type, nil, options)
-
end
-
end
-
-
9
def resolve_image_source(source, skip_pipeline)
-
if source.is_a?(Symbol) || source.is_a?(String)
-
path_to_image(source, skip_pipeline: skip_pipeline)
-
else
-
polymorphic_url(source)
-
end
-
rescue NoMethodError => e
-
raise ArgumentError, "Can't resolve image into URL: #{e}"
-
end
-
-
9
def extract_dimensions(size)
-
size = size.to_s
-
if /\A\d+x\d+\z/.match?(size)
-
size.split("x")
-
elsif /\A\d+\z/.match?(size)
-
[size, size]
-
end
-
end
-
-
9
def check_for_image_tag_errors(options)
-
if options[:size] && (options[:height] || options[:width])
-
raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
-
end
-
end
-
-
9
def resolve_link_as(extname, mime_type)
-
if extname == "js"
-
"script"
-
elsif extname == "css"
-
"style"
-
elsif extname == "vtt"
-
"track"
-
elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font))
-
type
-
end
-
end
-
-
9
def send_preload_links_header(preload_links)
-
if respond_to?(:request) && request
-
request.send_early_hints("Link" => preload_links.join("\n"))
-
end
-
-
if respond_to?(:response) && response
-
response.headers["Link"] = [response.headers["Link"].presence, *preload_links].compact.join(",")
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "zlib"
-
-
9
module ActionView
-
# = Action View Asset URL Helpers
-
9
module Helpers #:nodoc:
-
# This module provides methods for generating asset paths and
-
# URLs.
-
#
-
# image_path("rails.png")
-
# # => "/assets/rails.png"
-
#
-
# image_url("rails.png")
-
# # => "http://www.example.com/assets/rails.png"
-
#
-
# === Using asset hosts
-
#
-
# By default, Rails links to these assets on the current host in the public
-
# folder, but you can direct Rails to link to assets from a dedicated asset
-
# server by setting <tt>ActionController::Base.asset_host</tt> in the application
-
# configuration, typically in <tt>config/environments/production.rb</tt>.
-
# For example, you'd define <tt>assets.example.com</tt> to be your asset
-
# host this way, inside the <tt>configure</tt> block of your environment-specific
-
# configuration files or <tt>config/application.rb</tt>:
-
#
-
# config.action_controller.asset_host = "assets.example.com"
-
#
-
# Helpers take that into account:
-
#
-
# image_tag("rails.png")
-
# # => <img src="http://assets.example.com/assets/rails.png" />
-
# stylesheet_link_tag("application")
-
# # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
-
#
-
# Browsers open a limited number of simultaneous connections to a single
-
# host. The exact number varies by browser and version. This limit may cause
-
# some asset downloads to wait for previous assets to finish before they can
-
# begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to
-
# distribute the requests over four hosts. For example,
-
# <tt>assets%d.example.com</tt> will spread the asset requests over
-
# "assets0.example.com", ..., "assets3.example.com".
-
#
-
# image_tag("rails.png")
-
# # => <img src="http://assets0.example.com/assets/rails.png" />
-
# stylesheet_link_tag("application")
-
# # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
-
#
-
# This may improve the asset loading performance of your application.
-
# It is also possible the combination of additional connection overhead
-
# (DNS, SSL) and the overall browser connection limits may result in this
-
# solution being slower. You should be sure to measure your actual
-
# performance across targeted browsers both before and after this change.
-
#
-
# To implement the corresponding hosts you can either set up four actual
-
# hosts or use wildcard DNS to CNAME the wildcard to a single asset host.
-
# You can read more about setting up your DNS CNAME records from your ISP.
-
#
-
# Note: This is purely a browser performance optimization and is not meant
-
# for server load balancing. See https://www.die.net/musings/page_load_time/
-
# for background and https://www.browserscope.org/?category=network for
-
# connection limit data.
-
#
-
# Alternatively, you can exert more control over the asset host by setting
-
# +asset_host+ to a proc like this:
-
#
-
# ActionController::Base.asset_host = Proc.new { |source|
-
# "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
-
# }
-
# image_tag("rails.png")
-
# # => <img src="http://assets1.example.com/assets/rails.png" />
-
# stylesheet_link_tag("application")
-
# # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
-
#
-
# The example above generates "http://assets1.example.com" and
-
# "http://assets2.example.com". This option is useful for example if
-
# you need fewer/more than four hosts, custom host names, etc.
-
#
-
# As you see the proc takes a +source+ parameter. That's a string with the
-
# absolute path of the asset, for example "/assets/rails.png".
-
#
-
# ActionController::Base.asset_host = Proc.new { |source|
-
# if source.end_with?('.css')
-
# "http://stylesheets.example.com"
-
# else
-
# "http://assets.example.com"
-
# end
-
# }
-
# image_tag("rails.png")
-
# # => <img src="http://assets.example.com/assets/rails.png" />
-
# stylesheet_link_tag("application")
-
# # => <link href="http://stylesheets.example.com/assets/application.css" media="screen" rel="stylesheet" />
-
#
-
# Alternatively you may ask for a second parameter +request+. That one is
-
# particularly useful for serving assets from an SSL-protected page. The
-
# example proc below disables asset hosting for HTTPS connections, while
-
# still sending assets for plain HTTP requests from asset hosts. If you don't
-
# have SSL certificates for each of the asset hosts this technique allows you
-
# to avoid warnings in the client about mixed media.
-
# Note that the +request+ parameter might not be supplied, e.g. when the assets
-
# are precompiled with the command `bin/rails assets:precompile`. Make sure to use a
-
# +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them
-
# to +nil+.
-
#
-
# config.action_controller.asset_host = Proc.new { |source, request|
-
# if request && request.ssl?
-
# "#{request.protocol}#{request.host_with_port}"
-
# else
-
# "#{request.protocol}assets.example.com"
-
# end
-
# }
-
#
-
# You can also implement a custom asset host object that responds to +call+
-
# and takes either one or two parameters just like the proc.
-
#
-
# config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
-
# "http://asset%d.example.com", "https://asset1.example.com"
-
# )
-
#
-
9
module AssetUrlHelper
-
9
URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i
-
-
# This is the entry point for all assets.
-
# When using the asset pipeline (i.e. sprockets and sprockets-rails), the
-
# behavior is "enhanced". You can bypass the asset pipeline by passing in
-
# <tt>skip_pipeline: true</tt> to the options.
-
#
-
# All other asset *_path helpers delegate through this method.
-
#
-
# === With the asset pipeline
-
#
-
# All options passed to +asset_path+ will be passed to +compute_asset_path+
-
# which is implemented by sprockets-rails.
-
#
-
# asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js"
-
# asset_path('application.js', host: 'example.com') # => "//example.com/assets/application.js"
-
# asset_path("application.js", host: 'example.com', protocol: 'https') # => "https://example.com/assets/application.js"
-
#
-
# === Without the asset pipeline (<tt>skip_pipeline: true</tt>)
-
#
-
# Accepts a <tt>type</tt> option that can specify the asset's extension. No error
-
# checking is done to verify the source passed into +asset_path+ is valid
-
# and that the file exists on disk.
-
#
-
# asset_path("application.js", skip_pipeline: true) # => "application.js"
-
# asset_path("filedoesnotexist.png", skip_pipeline: true) # => "filedoesnotexist.png"
-
# asset_path("application", type: :javascript, skip_pipeline: true) # => "/javascripts/application.js"
-
# asset_path("application", type: :stylesheet, skip_pipeline: true) # => "/stylesheets/application.css"
-
#
-
# === Options applying to all assets
-
#
-
# Below lists scenarios that apply to +asset_path+ whether or not you're
-
# using the asset pipeline.
-
#
-
# - All fully qualified URLs are returned immediately. This bypasses the
-
# asset pipeline and all other behavior described.
-
#
-
# asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js"
-
#
-
# - All assets that begin with a forward slash are assumed to be full
-
# URLs and will not be expanded. This will bypass the asset pipeline.
-
#
-
# asset_path("/foo.png") # => "/foo.png"
-
#
-
# - All blank strings will be returned immediately. This bypasses the
-
# asset pipeline and all other behavior described.
-
#
-
# asset_path("") # => ""
-
#
-
# - If <tt>config.relative_url_root</tt> is specified, all assets will have that
-
# root prepended.
-
#
-
# Rails.application.config.relative_url_root = "bar"
-
# asset_path("foo.js", skip_pipeline: true) # => "bar/foo.js"
-
#
-
# - A different asset host can be specified via <tt>config.action_controller.asset_host</tt>
-
# this is commonly used in conjunction with a CDN.
-
#
-
# Rails.application.config.action_controller.asset_host = "assets.example.com"
-
# asset_path("foo.js", skip_pipeline: true) # => "http://assets.example.com/foo.js"
-
#
-
# - An extension name can be specified manually with <tt>extname</tt>.
-
#
-
# asset_path("foo", skip_pipeline: true, extname: ".js") # => "/foo.js"
-
# asset_path("foo.css", skip_pipeline: true, extname: ".js") # => "/foo.css.js"
-
9
def asset_path(source, options = {})
-
raise ArgumentError, "nil is not a valid asset source" if source.nil?
-
-
source = source.to_s
-
return "" if source.blank?
-
return source if URI_REGEXP.match?(source)
-
-
tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "")
-
-
if extname = compute_asset_extname(source, options)
-
source = "#{source}#{extname}"
-
end
-
-
if source[0] != ?/
-
if options[:skip_pipeline]
-
source = public_compute_asset_path(source, options)
-
else
-
source = compute_asset_path(source, options)
-
end
-
end
-
-
relative_url_root = defined?(config.relative_url_root) && config.relative_url_root
-
if relative_url_root
-
source = File.join(relative_url_root, source) unless source.start_with?("#{relative_url_root}/")
-
end
-
-
if host = compute_asset_host(source, options)
-
source = File.join(host, source)
-
end
-
-
"#{source}#{tail}"
-
end
-
9
alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route
-
-
# Computes the full URL to an asset in the public directory. This
-
# will use +asset_path+ internally, so most of their behaviors
-
# will be the same. If :host options is set, it overwrites global
-
# +config.action_controller.asset_host+ setting.
-
#
-
# All other options provided are forwarded to +asset_path+ call.
-
#
-
# asset_url "application.js" # => http://example.com/assets/application.js
-
# asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js
-
#
-
9
def asset_url(source, options = {})
-
path_to_asset(source, options.merge(protocol: :request))
-
end
-
9
alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route
-
-
9
ASSET_EXTENSIONS = {
-
javascript: ".js",
-
stylesheet: ".css"
-
}
-
-
# Compute extname to append to asset path. Returns +nil+ if
-
# nothing should be added.
-
9
def compute_asset_extname(source, options = {})
-
return if options[:extname] == false
-
extname = options[:extname] || ASSET_EXTENSIONS[options[:type]]
-
if extname && File.extname(source) != extname
-
extname
-
else
-
nil
-
end
-
end
-
-
# Maps asset types to public directory.
-
9
ASSET_PUBLIC_DIRECTORIES = {
-
audio: "/audios",
-
font: "/fonts",
-
image: "/images",
-
javascript: "/javascripts",
-
stylesheet: "/stylesheets",
-
video: "/videos"
-
}
-
-
# Computes asset path to public directory. Plugins and
-
# extensions can override this method to point to custom assets
-
# or generate digested paths or query strings.
-
9
def compute_asset_path(source, options = {})
-
dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || ""
-
File.join(dir, source)
-
end
-
9
alias :public_compute_asset_path :compute_asset_path
-
-
# Pick an asset host for this source. Returns +nil+ if no host is set,
-
# the host if no wildcard is set, the host interpolated with the
-
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
-
# or the value returned from invoking call on an object responding to call
-
# (proc or otherwise).
-
9
def compute_asset_host(source = "", options = {})
-
request = self.request if respond_to?(:request)
-
host = options[:host]
-
host ||= config.asset_host if defined? config.asset_host
-
-
if host
-
if host.respond_to?(:call)
-
arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
-
args = [source]
-
args << request if request && (arity > 1 || arity < 0)
-
host = host.call(*args)
-
elsif host.include?("%d")
-
host = host % (Zlib.crc32(source) % 4)
-
end
-
end
-
-
host ||= request.base_url if request && options[:protocol] == :request
-
return unless host
-
-
if URI_REGEXP.match?(host)
-
host
-
else
-
protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative)
-
case protocol
-
when :relative
-
"//#{host}"
-
when :request
-
"#{request.protocol}#{host}"
-
else
-
"#{protocol}://#{host}"
-
end
-
end
-
end
-
-
# Computes the path to a JavaScript asset in the public javascripts directory.
-
# If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
-
# Full paths from the document root will be passed through.
-
# Used internally by +javascript_include_tag+ to build the script path.
-
#
-
# javascript_path "xmlhr" # => /assets/xmlhr.js
-
# javascript_path "dir/xmlhr.js" # => /assets/dir/xmlhr.js
-
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
-
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
-
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
-
9
def javascript_path(source, options = {})
-
path_to_asset(source, { type: :javascript }.merge!(options))
-
end
-
9
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
-
-
# Computes the full URL to a JavaScript asset in the public javascripts directory.
-
# This will use +javascript_path+ internally, so most of their behaviors will be the same.
-
# Since +javascript_url+ is based on +asset_url+ method you can set :host options. If :host
-
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
-
#
-
# javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/js/xmlhr.js
-
#
-
9
def javascript_url(source, options = {})
-
url_to_asset(source, { type: :javascript }.merge!(options))
-
end
-
9
alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
-
-
# Computes the path to a stylesheet asset in the public stylesheets directory.
-
# If the +source+ filename has no extension, .css will be appended (except for explicit URIs).
-
# Full paths from the document root will be passed through.
-
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
-
#
-
# stylesheet_path "style" # => /assets/style.css
-
# stylesheet_path "dir/style.css" # => /assets/dir/style.css
-
# stylesheet_path "/dir/style.css" # => /dir/style.css
-
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
-
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
-
9
def stylesheet_path(source, options = {})
-
path_to_asset(source, { type: :stylesheet }.merge!(options))
-
end
-
9
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
-
-
# Computes the full URL to a stylesheet asset in the public stylesheets directory.
-
# This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
-
# Since +stylesheet_url+ is based on +asset_url+ method you can set :host options. If :host
-
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
-
#
-
# stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/assets/css/style.css
-
#
-
9
def stylesheet_url(source, options = {})
-
url_to_asset(source, { type: :stylesheet }.merge!(options))
-
end
-
9
alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
-
-
# Computes the path to an image asset.
-
# Full paths from the document root will be passed through.
-
# Used internally by +image_tag+ to build the image path:
-
#
-
# image_path("edit") # => "/assets/edit"
-
# image_path("edit.png") # => "/assets/edit.png"
-
# image_path("icons/edit.png") # => "/assets/icons/edit.png"
-
# image_path("/icons/edit.png") # => "/icons/edit.png"
-
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
-
#
-
# If you have images as application resources this method may conflict with their named routes.
-
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
-
# plugin authors are encouraged to do so.
-
9
def image_path(source, options = {})
-
path_to_asset(source, { type: :image }.merge!(options))
-
end
-
9
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
-
-
# Computes the full URL to an image asset.
-
# This will use +image_path+ internally, so most of their behaviors will be the same.
-
# Since +image_url+ is based on +asset_url+ method you can set :host options. If :host
-
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
-
#
-
# image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/assets/edit.png
-
#
-
9
def image_url(source, options = {})
-
url_to_asset(source, { type: :image }.merge!(options))
-
end
-
9
alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
-
-
# Computes the path to a video asset in the public videos directory.
-
# Full paths from the document root will be passed through.
-
# Used internally by +video_tag+ to build the video path.
-
#
-
# video_path("hd") # => /videos/hd
-
# video_path("hd.avi") # => /videos/hd.avi
-
# video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
-
# video_path("/trailers/hd.avi") # => /trailers/hd.avi
-
# video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
-
9
def video_path(source, options = {})
-
path_to_asset(source, { type: :video }.merge!(options))
-
end
-
9
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
-
-
# Computes the full URL to a video asset in the public videos directory.
-
# This will use +video_path+ internally, so most of their behaviors will be the same.
-
# Since +video_url+ is based on +asset_url+ method you can set :host options. If :host
-
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
-
#
-
# video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/videos/hd.avi
-
#
-
9
def video_url(source, options = {})
-
url_to_asset(source, { type: :video }.merge!(options))
-
end
-
9
alias_method :url_to_video, :video_url # aliased to avoid conflicts with a video_url named route
-
-
# Computes the path to an audio asset in the public audios directory.
-
# Full paths from the document root will be passed through.
-
# Used internally by +audio_tag+ to build the audio path.
-
#
-
# audio_path("horse") # => /audios/horse
-
# audio_path("horse.wav") # => /audios/horse.wav
-
# audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
-
# audio_path("/sounds/horse.wav") # => /sounds/horse.wav
-
# audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
-
9
def audio_path(source, options = {})
-
path_to_asset(source, { type: :audio }.merge!(options))
-
end
-
9
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
-
-
# Computes the full URL to an audio asset in the public audios directory.
-
# This will use +audio_path+ internally, so most of their behaviors will be the same.
-
# Since +audio_url+ is based on +asset_url+ method you can set :host options. If :host
-
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
-
#
-
# audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/audios/horse.wav
-
#
-
9
def audio_url(source, options = {})
-
url_to_asset(source, { type: :audio }.merge!(options))
-
end
-
9
alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
-
-
# Computes the path to a font asset.
-
# Full paths from the document root will be passed through.
-
#
-
# font_path("font") # => /fonts/font
-
# font_path("font.ttf") # => /fonts/font.ttf
-
# font_path("dir/font.ttf") # => /fonts/dir/font.ttf
-
# font_path("/dir/font.ttf") # => /dir/font.ttf
-
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
-
9
def font_path(source, options = {})
-
path_to_asset(source, { type: :font }.merge!(options))
-
end
-
9
alias_method :path_to_font, :font_path # aliased to avoid conflicts with a font_path named route
-
-
# Computes the full URL to a font asset.
-
# This will use +font_path+ internally, so most of their behaviors will be the same.
-
# Since +font_url+ is based on +asset_url+ method you can set :host options. If :host
-
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
-
#
-
# font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/fonts/font.ttf
-
#
-
9
def font_url(source, options = {})
-
url_to_asset(source, { type: :font }.merge!(options))
-
end
-
9
alias_method :url_to_font, :font_url # aliased to avoid conflicts with a font_url named route
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "set"
-
9
require "active_support/core_ext/symbol/starts_ends_with"
-
-
9
module ActionView
-
# = Action View Atom Feed Helpers
-
9
module Helpers #:nodoc:
-
9
module AtomFeedHelper
-
# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
-
# template languages).
-
#
-
# Full usage example:
-
#
-
# config/routes.rb:
-
# Rails.application.routes.draw do
-
# resources :posts
-
# root to: "posts#index"
-
# end
-
#
-
# app/controllers/posts_controller.rb:
-
# class PostsController < ApplicationController
-
# # GET /posts.html
-
# # GET /posts.atom
-
# def index
-
# @posts = Post.all
-
#
-
# respond_to do |format|
-
# format.html
-
# format.atom
-
# end
-
# end
-
# end
-
#
-
# app/views/posts/index.atom.builder:
-
# atom_feed do |feed|
-
# feed.title("My great blog!")
-
# feed.updated(@posts[0].created_at) if @posts.length > 0
-
#
-
# @posts.each do |post|
-
# feed.entry(post) do |entry|
-
# entry.title(post.title)
-
# entry.content(post.body, type: 'html')
-
#
-
# entry.author do |author|
-
# author.name("DHH")
-
# end
-
# end
-
# end
-
# end
-
#
-
# The options for atom_feed are:
-
#
-
# * <tt>:language</tt>: Defaults to "en-US".
-
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
-
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
-
# * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
-
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
-
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
-
# 2005 is used (as an "I don't care" value).
-
# * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
-
#
-
# Other namespaces can be added to the root element:
-
#
-
# app/views/posts/index.atom.builder:
-
# atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
-
# 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
-
# feed.title("My great blog!")
-
# feed.updated((@posts.first.created_at))
-
# feed.tag!('openSearch:totalResults', 10)
-
#
-
# @posts.each do |post|
-
# feed.entry(post) do |entry|
-
# entry.title(post.title)
-
# entry.content(post.body, type: 'html')
-
# entry.tag!('app:edited', Time.now)
-
#
-
# entry.author do |author|
-
# author.name("DHH")
-
# end
-
# end
-
# end
-
# end
-
#
-
# The Atom spec defines five elements (content rights title subtitle
-
# summary) which may directly contain xhtml content if type: 'xhtml'
-
# is specified as an attribute. If so, this helper will take care of
-
# the enclosing div and xhtml namespace declaration. Example usage:
-
#
-
# entry.summary type: 'xhtml' do |xhtml|
-
# xhtml.p pluralize(order.line_items.count, "line item")
-
# xhtml.p "Shipped to #{order.address}"
-
# xhtml.p "Paid by #{order.pay_type}"
-
# end
-
#
-
#
-
# <tt>atom_feed</tt> yields an +AtomFeedBuilder+ instance. Nested elements yield
-
# an +AtomBuilder+ instance.
-
9
def atom_feed(options = {}, &block)
-
if options[:schema_date]
-
options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime)
-
else
-
options[:schema_date] = "2005" # The Atom spec copyright date
-
end
-
-
xml = options.delete(:xml) || eval("xml", block.binding)
-
xml.instruct!
-
if options[:instruct]
-
options[:instruct].each do |target, attrs|
-
if attrs.respond_to?(:keys)
-
xml.instruct!(target, attrs)
-
elsif attrs.respond_to?(:each)
-
attrs.each { |attr_group| xml.instruct!(target, attr_group) }
-
end
-
end
-
end
-
-
feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" }
-
feed_opts.merge!(options).select! { |k, _| k.start_with?("xml") }
-
-
xml.feed(feed_opts) do
-
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
-
xml.link(rel: "alternate", type: "text/html", href: options[:root_url] || (request.protocol + request.host_with_port))
-
xml.link(rel: "self", type: "application/atom+xml", href: options[:url] || request.url)
-
-
yield AtomFeedBuilder.new(xml, self, options)
-
end
-
end
-
-
9
class AtomBuilder #:nodoc:
-
9
XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
-
-
9
def initialize(xml)
-
@xml = xml
-
end
-
-
9
private
-
# Delegate to xml builder, first wrapping the element in an xhtml
-
# namespaced div element if the method and arguments indicate
-
# that an xhtml_block? is desired.
-
9
def method_missing(method, *arguments, &block)
-
if xhtml_block?(method, arguments)
-
@xml.__send__(method, *arguments) do
-
@xml.div(xmlns: "http://www.w3.org/1999/xhtml") do |xhtml|
-
block.call(xhtml)
-
end
-
end
-
else
-
@xml.__send__(method, *arguments, &block)
-
end
-
end
-
-
# True if the method name matches one of the five elements defined
-
# in the Atom spec as potentially containing XHTML content and
-
# if type: 'xhtml' is, in fact, specified.
-
9
def xhtml_block?(method, arguments)
-
if XHTML_TAG_NAMES.include?(method.to_s)
-
last = arguments.last
-
last.is_a?(Hash) && last[:type].to_s == "xhtml"
-
end
-
end
-
end
-
-
9
class AtomFeedBuilder < AtomBuilder #:nodoc:
-
9
def initialize(xml, view, feed_options = {})
-
@xml, @view, @feed_options = xml, view, feed_options
-
end
-
-
# Accepts a Date or Time object and inserts it in the proper format. If +nil+ is passed, current time in UTC is used.
-
9
def updated(date_or_time = nil)
-
@xml.updated((date_or_time || Time.now.utc).xmlschema)
-
end
-
-
# Creates an entry tag for a specific record and prefills the id using class and id.
-
#
-
# Options:
-
#
-
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
-
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
-
# * <tt>:url</tt>: The URL for this entry or +false+ or +nil+ for not having a link tag. Defaults to the +polymorphic_url+ for the record.
-
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
-
# * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
-
9
def entry(record, options = {})
-
@xml.entry do
-
@xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
-
-
if options[:published] || (record.respond_to?(:created_at) && record.created_at)
-
@xml.published((options[:published] || record.created_at).xmlschema)
-
end
-
-
if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
-
@xml.updated((options[:updated] || record.updated_at).xmlschema)
-
end
-
-
type = options.fetch(:type, "text/html")
-
-
url = options.fetch(:url) { @view.polymorphic_url(record) }
-
@xml.link(rel: "alternate", type: type, href: url) if url
-
-
yield AtomBuilder.new(@xml)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
# = Action View Cache Helper
-
9
module Helpers #:nodoc:
-
9
module CacheHelper
-
# This helper exposes a method for caching fragments of a view
-
# rather than an entire action or page. This technique is useful
-
# caching pieces like menus, lists of new topics, static HTML
-
# fragments, and so on. This method takes a block that contains
-
# the content you wish to cache.
-
#
-
# The best way to use this is by doing recyclable key-based cache expiration
-
# on top of a cache store like Memcached or Redis that'll automatically
-
# kick out old entries.
-
#
-
# When using this method, you list the cache dependency as the name of the cache, like so:
-
#
-
# <% cache project do %>
-
# <b>All the topics on this project</b>
-
# <%= render project.topics %>
-
# <% end %>
-
#
-
# This approach will assume that when a new topic is added, you'll touch
-
# the project. The cache key generated from this call will be something like:
-
#
-
# views/template/action:7a1156131a6928cb0026877f8b749ac9/projects/123
-
# ^template path ^template tree digest ^class ^id
-
#
-
# This cache key is stable, but it's combined with a cache version derived from the project
-
# record. When the project updated_at is touched, the #cache_version changes, even
-
# if the key stays stable. This means that unlike a traditional key-based cache expiration
-
# approach, you won't be generating cache trash, unused keys, simply because the dependent
-
# record is updated.
-
#
-
# If your template cache depends on multiple sources (try to avoid this to keep things simple),
-
# you can name all these dependencies as part of an array:
-
#
-
# <% cache [ project, current_user ] do %>
-
# <b>All the topics on this project</b>
-
# <%= render project.topics %>
-
# <% end %>
-
#
-
# This will include both records as part of the cache key and updating either of them will
-
# expire the cache.
-
#
-
# ==== \Template digest
-
#
-
# The template digest that's added to the cache key is computed by taking an MD5 of the
-
# contents of the entire template file. This ensures that your caches will automatically
-
# expire when you change the template file.
-
#
-
# Note that the MD5 is taken of the entire template file, not just what's within the
-
# cache do/end call. So it's possible that changing something outside of that call will
-
# still expire the cache.
-
#
-
# Additionally, the digestor will automatically look through your template file for
-
# explicit and implicit dependencies, and include those as part of the digest.
-
#
-
# The digestor can be bypassed by passing skip_digest: true as an option to the cache call:
-
#
-
# <% cache project, skip_digest: true do %>
-
# <b>All the topics on this project</b>
-
# <%= render project.topics %>
-
# <% end %>
-
#
-
# ==== Implicit dependencies
-
#
-
# Most template dependencies can be derived from calls to render in the template itself.
-
# Here are some examples of render calls that Cache Digests knows how to decode:
-
#
-
# render partial: "comments/comment", collection: commentable.comments
-
# render "comments/comments"
-
# render 'comments/comments'
-
# render('comments/comments')
-
#
-
# render "header" translates to render("comments/header")
-
#
-
# render(@topic) translates to render("topics/topic")
-
# render(topics) translates to render("topics/topic")
-
# render(message.topics) translates to render("topics/topic")
-
#
-
# It's not possible to derive all render calls like that, though.
-
# Here are a few examples of things that can't be derived:
-
#
-
# render group_of_attachments
-
# render @project.documents.where(published: true).order('created_at')
-
#
-
# You will have to rewrite those to the explicit form:
-
#
-
# render partial: 'attachments/attachment', collection: group_of_attachments
-
# render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')
-
#
-
# === Explicit dependencies
-
#
-
# Sometimes you'll have template dependencies that can't be derived at all. This is typically
-
# the case when you have template rendering that happens in helpers. Here's an example:
-
#
-
# <%= render_sortable_todolists @project.todolists %>
-
#
-
# You'll need to use a special comment format to call those out:
-
#
-
# <%# Template Dependency: todolists/todolist %>
-
# <%= render_sortable_todolists @project.todolists %>
-
#
-
# In some cases, like a single table inheritance setup, you might have
-
# a bunch of explicit dependencies. Instead of writing every template out,
-
# you can use a wildcard to match any template in a directory:
-
#
-
# <%# Template Dependency: events/* %>
-
# <%= render_categorizable_events @person.events %>
-
#
-
# This marks every template in the directory as a dependency. To find those
-
# templates, the wildcard path must be absolutely defined from <tt>app/views</tt> or paths
-
# otherwise added with +prepend_view_path+ or +append_view_path+.
-
# This way the wildcard for <tt>app/views/recordings/events</tt> would be <tt>recordings/events/*</tt> etc.
-
#
-
# The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>,
-
# so it's important that you type it out just so.
-
# You can only declare one template dependency per line.
-
#
-
# === External dependencies
-
#
-
# If you use a helper method, for example, inside a cached block and
-
# you then update that helper, you'll have to bump the cache as well.
-
# It doesn't really matter how you do it, but the MD5 of the template file
-
# must change. One recommendation is to simply be explicit in a comment, like:
-
#
-
# <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
-
# <%= some_helper_method(person) %>
-
#
-
# Now all you have to do is change that timestamp when the helper method changes.
-
#
-
# === Collection Caching
-
#
-
# When rendering a collection of objects that each use the same partial, a <tt>:cached</tt>
-
# option can be passed.
-
#
-
# For collections rendered such:
-
#
-
# <%= render partial: 'projects/project', collection: @projects, cached: true %>
-
#
-
# The <tt>cached: true</tt> will make Action View's rendering read several templates
-
# from cache at once instead of one call per template.
-
#
-
# Templates in the collection not already cached are written to cache.
-
#
-
# Works great alongside individual template fragment caching.
-
# For instance if the template the collection renders is cached like:
-
#
-
# # projects/_project.html.erb
-
# <% cache project do %>
-
# <%# ... %>
-
# <% end %>
-
#
-
# Any collection renders will find those cached templates when attempting
-
# to read multiple templates at once.
-
#
-
# If your collection cache depends on multiple sources (try to avoid this to keep things simple),
-
# you can name all these dependencies as part of a block that returns an array:
-
#
-
# <%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %>
-
#
-
# This will include both records as part of the cache key and updating either of them will
-
# expire the cache.
-
9
def cache(name = {}, options = {}, &block)
-
if controller.respond_to?(:perform_caching) && controller.perform_caching
-
name_options = options.slice(:skip_digest)
-
safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
-
else
-
yield
-
end
-
-
nil
-
end
-
-
# Cache fragments of a view if +condition+ is true
-
#
-
# <% cache_if admin?, project do %>
-
# <b>All the topics on this project</b>
-
# <%= render project.topics %>
-
# <% end %>
-
9
def cache_if(condition, name = {}, options = {}, &block)
-
if condition
-
cache(name, options, &block)
-
else
-
yield
-
end
-
-
nil
-
end
-
-
# Cache fragments of a view unless +condition+ is true
-
#
-
# <% cache_unless admin?, project do %>
-
# <b>All the topics on this project</b>
-
# <%= render project.topics %>
-
# <% end %>
-
9
def cache_unless(condition, name = {}, options = {}, &block)
-
cache_if !condition, name, options, &block
-
end
-
-
# This helper returns the name of a cache key for a given fragment cache
-
# call. By supplying <tt>skip_digest: true</tt> to cache, the digestion of cache
-
# fragments can be manually bypassed. This is useful when cache fragments
-
# cannot be manually expired unless you know the exact key which is the
-
# case when using memcached.
-
9
def cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil)
-
if skip_digest
-
name
-
else
-
fragment_name_with_digest(name, digest_path)
-
end
-
end
-
-
9
def digest_path_from_template(template) # :nodoc:
-
digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies)
-
-
if digest.present?
-
"#{template.virtual_path}:#{digest}"
-
else
-
template.virtual_path
-
end
-
end
-
-
9
private
-
9
def fragment_name_with_digest(name, digest_path)
-
name = controller.url_for(name).split("://").last if name.is_a?(Hash)
-
-
if @current_template&.virtual_path || digest_path
-
digest_path ||= digest_path_from_template(@current_template)
-
[ digest_path, name ]
-
else
-
name
-
end
-
end
-
-
9
def fragment_for(name = {}, options = nil, &block)
-
if content = read_fragment_for(name, options)
-
@view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer)
-
content
-
else
-
@view_renderer.cache_hits[@current_template&.virtual_path] = :miss if defined?(@view_renderer)
-
write_fragment_for(name, options, &block)
-
end
-
end
-
-
9
def read_fragment_for(name, options)
-
controller.read_fragment(name, options)
-
end
-
-
9
def write_fragment_for(name, options)
-
pos = output_buffer.length
-
yield
-
output_safe = output_buffer.html_safe?
-
fragment = output_buffer.slice!(pos..-1)
-
if output_safe
-
self.output_buffer = output_buffer.class.new(output_buffer)
-
end
-
controller.write_fragment(name, fragment, options)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "active_support/core_ext/string/output_safety"
-
-
9
module ActionView
-
# = Action View Capture Helper
-
9
module Helpers #:nodoc:
-
# CaptureHelper exposes methods to let you extract generated markup which
-
# can be used in other parts of a template or layout file.
-
#
-
# It provides a method to capture blocks into variables through capture and
-
# a way to capture a block of markup for use in a layout through {content_for}[rdoc-ref:ActionView::Helpers::CaptureHelper#content_for].
-
9
module CaptureHelper
-
# The capture method extracts part of a template as a String object.
-
# You can then use this object anywhere in your templates, layout, or helpers.
-
#
-
# The capture method can be used in ERB templates...
-
#
-
# <% @greeting = capture do %>
-
# Welcome to my shiny new web page! The date and time is
-
# <%= Time.now %>
-
# <% end %>
-
#
-
# ...and Builder (RXML) templates.
-
#
-
# @timestamp = capture do
-
# "The current timestamp is #{Time.now}."
-
# end
-
#
-
# You can then use that variable anywhere else. For example:
-
#
-
# <html>
-
# <head><title><%= @greeting %></title></head>
-
# <body>
-
# <b><%= @greeting %></b>
-
# </body>
-
# </html>
-
#
-
# The return of capture is the string generated by the block. For Example:
-
#
-
# @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500"
-
#
-
9
def capture(*args)
-
value = nil
-
buffer = with_output_buffer { value = yield(*args) }
-
if (string = buffer.presence || value) && string.is_a?(String)
-
ERB::Util.html_escape string
-
end
-
end
-
-
# Calling <tt>content_for</tt> stores a block of markup in an identifier for later use.
-
# In order to access this stored content in other templates, helper modules
-
# or the layout, you would pass the identifier as an argument to <tt>content_for</tt>.
-
#
-
# Note: <tt>yield</tt> can still be used to retrieve the stored content, but calling
-
# <tt>yield</tt> doesn't work in helper modules, while <tt>content_for</tt> does.
-
#
-
# <% content_for :not_authorized do %>
-
# alert('You are not authorized to do that!')
-
# <% end %>
-
#
-
# You can then use <tt>content_for :not_authorized</tt> anywhere in your templates.
-
#
-
# <%= content_for :not_authorized if current_user.nil? %>
-
#
-
# This is equivalent to:
-
#
-
# <%= yield :not_authorized if current_user.nil? %>
-
#
-
# <tt>content_for</tt>, however, can also be used in helper modules.
-
#
-
# module StorageHelper
-
# def stored_content
-
# content_for(:storage) || "Your storage is empty"
-
# end
-
# end
-
#
-
# This helper works just like normal helpers.
-
#
-
# <%= stored_content %>
-
#
-
# You can also use the <tt>yield</tt> syntax alongside an existing call to
-
# <tt>yield</tt> in a layout. For example:
-
#
-
# <%# This is the layout %>
-
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-
# <head>
-
# <title>My Website</title>
-
# <%= yield :script %>
-
# </head>
-
# <body>
-
# <%= yield %>
-
# </body>
-
# </html>
-
#
-
# And now, we'll create a view that has a <tt>content_for</tt> call that
-
# creates the <tt>script</tt> identifier.
-
#
-
# <%# This is our view %>
-
# Please login!
-
#
-
# <% content_for :script do %>
-
# <script>alert('You are not authorized to view this page!')</script>
-
# <% end %>
-
#
-
# Then, in another view, you could to do something like this:
-
#
-
# <%= link_to 'Logout', action: 'logout', remote: true %>
-
#
-
# <% content_for :script do %>
-
# <%= javascript_include_tag :defaults %>
-
# <% end %>
-
#
-
# That will place +script+ tags for your default set of JavaScript files on the page;
-
# this technique is useful if you'll only be using these scripts in a few views.
-
#
-
# Note that <tt>content_for</tt> concatenates (default) the blocks it is given for a particular
-
# identifier in order. For example:
-
#
-
# <% content_for :navigation do %>
-
# <li><%= link_to 'Home', action: 'index' %></li>
-
# <% end %>
-
#
-
# And in another place:
-
#
-
# <% content_for :navigation do %>
-
# <li><%= link_to 'Login', action: 'login' %></li>
-
# <% end %>
-
#
-
# Then, in another template or layout, this code would render both links in order:
-
#
-
# <ul><%= content_for :navigation %></ul>
-
#
-
# If the flush parameter is +true+ <tt>content_for</tt> replaces the blocks it is given. For example:
-
#
-
# <% content_for :navigation do %>
-
# <li><%= link_to 'Home', action: 'index' %></li>
-
# <% end %>
-
#
-
# <%# Add some other content, or use a different template: %>
-
#
-
# <% content_for :navigation, flush: true do %>
-
# <li><%= link_to 'Login', action: 'login' %></li>
-
# <% end %>
-
#
-
# Then, in another template or layout, this code would render only the last link:
-
#
-
# <ul><%= content_for :navigation %></ul>
-
#
-
# Lastly, simple content can be passed as a parameter:
-
#
-
# <% content_for :script, javascript_include_tag(:defaults) %>
-
#
-
# WARNING: <tt>content_for</tt> is ignored in caches. So you shouldn't use it for elements that will be fragment cached.
-
9
def content_for(name, content = nil, options = {}, &block)
-
if content || block_given?
-
if block_given?
-
options = content if content
-
content = capture(&block)
-
end
-
if content
-
options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content)
-
end
-
nil
-
else
-
@view_flow.get(name).presence
-
end
-
end
-
-
# The same as +content_for+ but when used with streaming flushes
-
# straight back to the layout. In other words, if you want to
-
# concatenate several times to the same buffer when rendering a given
-
# template, you should use +content_for+, if not, use +provide+ to tell
-
# the layout to stop looking for more contents.
-
9
def provide(name, content = nil, &block)
-
content = capture(&block) if block_given?
-
result = @view_flow.append!(name, content) if content
-
result unless content
-
end
-
-
# <tt>content_for?</tt> checks whether any content has been captured yet using <tt>content_for</tt>.
-
# Useful to render parts of your layout differently based on what is in your views.
-
#
-
# <%# This is the layout %>
-
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-
# <head>
-
# <title>My Website</title>
-
# <%= yield :script %>
-
# </head>
-
# <body class="<%= content_for?(:right_col) ? 'two-column' : 'one-column' %>">
-
# <%= yield %>
-
# <%= yield :right_col %>
-
# </body>
-
# </html>
-
9
def content_for?(name)
-
@view_flow.get(name).present?
-
end
-
-
# Use an alternate output buffer for the duration of the block.
-
# Defaults to a new empty string.
-
9
def with_output_buffer(buf = nil) #:nodoc:
-
unless buf
-
buf = ActionView::OutputBuffer.new
-
if output_buffer && output_buffer.respond_to?(:encoding)
-
buf.force_encoding(output_buffer.encoding)
-
end
-
end
-
self.output_buffer, old_buffer = buf, output_buffer
-
yield
-
output_buffer
-
ensure
-
self.output_buffer = old_buffer
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "active_support/core_ext/module/attr_internal"
-
-
9
module ActionView
-
9
module Helpers #:nodoc:
-
# This module keeps all methods and behavior in ActionView
-
# that simply delegates to the controller.
-
9
module ControllerHelper #:nodoc:
-
9
attr_internal :controller, :request
-
-
9
CONTROLLER_DELEGATES = [:request_forgery_protection_token, :params,
-
:session, :cookies, :response, :headers, :flash, :action_name,
-
:controller_name, :controller_path]
-
-
9
delegate(*CONTROLLER_DELEGATES, to: :controller)
-
-
9
def assign_controller(controller)
-
if @_controller = controller
-
@_request = controller.request if controller.respond_to?(:request)
-
@_config = controller.config.inheritable_copy if controller.respond_to?(:config)
-
@_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder)
-
end
-
end
-
-
9
def logger
-
controller.logger if controller.respond_to?(:logger)
-
end
-
-
9
def respond_to?(method_name, include_private = false)
-
return controller.respond_to?(method_name) if CONTROLLER_DELEGATES.include?(method_name.to_sym)
-
super
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
# = Action View CSP Helper
-
9
module Helpers #:nodoc:
-
9
module CspHelper
-
# Returns a meta tag "csp-nonce" with the per-session nonce value
-
# for allowing inline <script> tags.
-
#
-
# <head>
-
# <%= csp_meta_tag %>
-
# </head>
-
#
-
# This is used by the Rails UJS helper to create dynamically
-
# loaded inline <script> elements.
-
#
-
9
def csp_meta_tag(**options)
-
if content_security_policy?
-
options[:name] = "csp-nonce"
-
options[:content] = content_security_policy_nonce
-
tag("meta", options)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
# = Action View CSRF Helper
-
9
module Helpers #:nodoc:
-
9
module CsrfHelper
-
# Returns meta tags "csrf-param" and "csrf-token" with the name of the cross-site
-
# request forgery protection parameter and token, respectively.
-
#
-
# <head>
-
# <%= csrf_meta_tags %>
-
# </head>
-
#
-
# These are used to generate the dynamic forms that implement non-remote links with
-
# <tt>:method</tt>.
-
#
-
# You don't need to use these tags for regular forms as they generate their own hidden fields.
-
#
-
# For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the
-
# "X-CSRF-Token" HTTP header. If you are using rails-ujs this happens automatically.
-
#
-
9
def csrf_meta_tags
-
if defined?(protect_against_forgery?) && protect_against_forgery?
-
[
-
tag("meta", name: "csrf-param", content: request_forgery_protection_token),
-
tag("meta", name: "csrf-token", content: form_authenticity_token)
-
].join("\n").html_safe
-
end
-
end
-
-
# For backwards compatibility.
-
9
alias csrf_meta_tag csrf_meta_tags
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "date"
-
9
require "action_view/helpers/tag_helper"
-
9
require "active_support/core_ext/array/extract_options"
-
9
require "active_support/core_ext/date/conversions"
-
9
require "active_support/core_ext/hash/slice"
-
9
require "active_support/core_ext/object/acts_like"
-
9
require "active_support/core_ext/object/with_options"
-
-
9
module ActionView
-
9
module Helpers #:nodoc:
-
# = Action View Date Helpers
-
#
-
# The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time
-
# elements. All of the select-type methods share a number of common options that are as follows:
-
#
-
# * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday"
-
# would give \birthday[month] instead of \date[month] if passed to the <tt>select_month</tt> method.
-
# * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
-
# * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true,
-
# the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead
-
# of \date[month].
-
9
module DateHelper
-
9
MINUTES_IN_YEAR = 525600
-
9
MINUTES_IN_QUARTER_YEAR = 131400
-
9
MINUTES_IN_THREE_QUARTERS_YEAR = 394200
-
-
# Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
-
# Pass <tt>include_seconds: true</tt> if you want more detailed approximations when distance < 1 min, 29 secs.
-
# Distances are reported based on the following table:
-
#
-
# 0 <-> 29 secs # => less than a minute
-
# 30 secs <-> 1 min, 29 secs # => 1 minute
-
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
-
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
-
# 89 mins, 30 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
-
# 23 hrs, 59 mins, 30 secs <-> 41 hrs, 59 mins, 29 secs # => 1 day
-
# 41 hrs, 59 mins, 30 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
-
# 29 days, 23 hrs, 59 mins, 30 secs <-> 44 days, 23 hrs, 59 mins, 29 secs # => about 1 month
-
# 44 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 2 months
-
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
-
# 1 yr <-> 1 yr, 3 months # => about 1 year
-
# 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year
-
# 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years
-
# 2 yrs <-> max time or date # => (same rules as 1 yr)
-
#
-
# With <tt>include_seconds: true</tt> and the difference < 1 minute 29 seconds:
-
# 0-4 secs # => less than 5 seconds
-
# 5-9 secs # => less than 10 seconds
-
# 10-19 secs # => less than 20 seconds
-
# 20-39 secs # => half a minute
-
# 40-59 secs # => less than a minute
-
# 60-89 secs # => 1 minute
-
#
-
# from_time = Time.now
-
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
-
# distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
-
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
-
# distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds
-
# distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
-
# distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days
-
# distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute
-
# distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute
-
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
-
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
-
# distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
-
# distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years
-
#
-
# to_time = Time.now + 6.years + 19.days
-
# distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years
-
# distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years
-
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
-
#
-
# With the <tt>scope</tt> option, you can define a custom scope for Rails
-
# to look up the translation.
-
#
-
# For example you can define the following in your locale (e.g. en.yml).
-
#
-
# datetime:
-
# distance_in_words:
-
# short:
-
# about_x_hours:
-
# one: 'an hour'
-
# other: '%{count} hours'
-
#
-
# See https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en.yml
-
# for more examples.
-
#
-
# Which will then result in the following:
-
#
-
# from_time = Time.now
-
# distance_of_time_in_words(from_time, from_time + 50.minutes, scope: 'datetime.distance_in_words.short') # => "an hour"
-
# distance_of_time_in_words(from_time, from_time + 3.hours, scope: 'datetime.distance_in_words.short') # => "3 hours"
-
9
def distance_of_time_in_words(from_time, to_time = 0, options = {})
-
options = {
-
scope: :'datetime.distance_in_words'
-
}.merge!(options)
-
-
from_time = normalize_distance_of_time_argument_to_time(from_time)
-
to_time = normalize_distance_of_time_argument_to_time(to_time)
-
from_time, to_time = to_time, from_time if from_time > to_time
-
distance_in_minutes = ((to_time - from_time) / 60.0).round
-
distance_in_seconds = (to_time - from_time).round
-
-
I18n.with_options locale: options[:locale], scope: options[:scope] do |locale|
-
case distance_in_minutes
-
when 0..1
-
return distance_in_minutes == 0 ?
-
locale.t(:less_than_x_minutes, count: 1) :
-
locale.t(:x_minutes, count: distance_in_minutes) unless options[:include_seconds]
-
-
case distance_in_seconds
-
when 0..4 then locale.t :less_than_x_seconds, count: 5
-
when 5..9 then locale.t :less_than_x_seconds, count: 10
-
when 10..19 then locale.t :less_than_x_seconds, count: 20
-
when 20..39 then locale.t :half_a_minute
-
when 40..59 then locale.t :less_than_x_minutes, count: 1
-
else locale.t :x_minutes, count: 1
-
end
-
-
when 2...45 then locale.t :x_minutes, count: distance_in_minutes
-
when 45...90 then locale.t :about_x_hours, count: 1
-
# 90 mins up to 24 hours
-
when 90...1440 then locale.t :about_x_hours, count: (distance_in_minutes.to_f / 60.0).round
-
# 24 hours up to 42 hours
-
when 1440...2520 then locale.t :x_days, count: 1
-
# 42 hours up to 30 days
-
when 2520...43200 then locale.t :x_days, count: (distance_in_minutes.to_f / 1440.0).round
-
# 30 days up to 60 days
-
when 43200...86400 then locale.t :about_x_months, count: (distance_in_minutes.to_f / 43200.0).round
-
# 60 days up to 365 days
-
when 86400...525600 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round
-
else
-
from_year = from_time.year
-
from_year += 1 if from_time.month >= 3
-
to_year = to_time.year
-
to_year -= 1 if to_time.month < 3
-
leap_years = (from_year > to_year) ? 0 : (from_year..to_year).count { |x| Date.leap?(x) }
-
minute_offset_for_leap_year = leap_years * 1440
-
# Discount the leap year days when calculating year distance.
-
# e.g. if there are 20 leap year days between 2 dates having the same day
-
# and month then the based on 365 days calculation
-
# the distance in years will come out to over 80 years when in written
-
# English it would read better as about 80 years.
-
minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
-
remainder = (minutes_with_offset % MINUTES_IN_YEAR)
-
distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR)
-
if remainder < MINUTES_IN_QUARTER_YEAR
-
locale.t(:about_x_years, count: distance_in_years)
-
elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR
-
locale.t(:over_x_years, count: distance_in_years)
-
else
-
locale.t(:almost_x_years, count: distance_in_years + 1)
-
end
-
end
-
end
-
end
-
-
# Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
-
#
-
# time_ago_in_words(3.minutes.from_now) # => 3 minutes
-
# time_ago_in_words(3.minutes.ago) # => 3 minutes
-
# time_ago_in_words(Time.now - 15.hours) # => about 15 hours
-
# time_ago_in_words(Time.now) # => less than a minute
-
# time_ago_in_words(Time.now, include_seconds: true) # => less than 5 seconds
-
#
-
# from_time = Time.now - 3.days - 14.minutes - 25.seconds
-
# time_ago_in_words(from_time) # => 3 days
-
#
-
# from_time = (3.days + 14.minutes + 25.seconds).ago
-
# time_ago_in_words(from_time) # => 3 days
-
#
-
# Note that you cannot pass a <tt>Numeric</tt> value to <tt>time_ago_in_words</tt>.
-
#
-
9
def time_ago_in_words(from_time, options = {})
-
distance_of_time_in_words(from_time, Time.now, options)
-
end
-
-
9
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
-
-
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
-
# attribute (identified by +method+) on an object assigned to the template (identified by +object+).
-
#
-
# ==== Options
-
# * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g.
-
# "2" instead of "February").
-
# * <tt>:use_two_digit_numbers</tt> - Set to true if you want to display two digit month and day numbers (e.g.
-
# "02" instead of "February" and "08" instead of "8").
-
# * <tt>:use_short_month</tt> - Set to true if you want to use abbreviated month names instead of full
-
# month names (e.g. "Feb" instead of "February").
-
# * <tt>:add_month_numbers</tt> - Set to true if you want to use both month numbers and month names (e.g.
-
# "2 - February" instead of "February").
-
# * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names.
-
# Note: You can also use Rails' i18n functionality for this.
-
# * <tt>:month_format_string</tt> - Set to a format string. The string gets passed keys +:number+ (integer)
-
# and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
-
# See <tt>Kernel.sprintf</tt> for documentation on format sequences.
-
# * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
-
# * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is " : ".
-
# * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is " — ".
-
# * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if
-
# you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
-
# the current selected year minus 5.
-
# * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
-
# you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to
-
# the current selected year plus 5.
-
# * <tt>:year_format</tt> - Set format of years for year select. Lambda should be passed.
-
# * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
-
# as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
-
# first of the given month in order to not create invalid dates like 31 February.
-
# * <tt>:discard_month</tt> - Set to true if you don't want to show a month select. This includes the month
-
# as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true.
-
# * <tt>:discard_year</tt> - Set to true if you don't want to show a year select. This includes the year
-
# as a hidden field instead of showing a select field.
-
# * <tt>:order</tt> - Set to an array containing <tt>:day</tt>, <tt>:month</tt> and <tt>:year</tt> to
-
# customize the order in which the select fields are shown. If you leave out any of the symbols, the respective
-
# select will not be shown (like when you set <tt>discard_xxx: true</tt>. Defaults to the order defined in
-
# the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails).
-
# * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty
-
# dates.
-
# * <tt>:default</tt> - Set a default date if the affected date isn't set or is +nil+.
-
# * <tt>:selected</tt> - Set a date that overrides the actual value.
-
# * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
-
# * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings
-
# for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
-
# Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
-
# or the given prompt string.
-
# * <tt>:with_css_classes</tt> - Set to true or a hash of strings. Use true if you want to assign generic styles for
-
# select tags. This automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second'. A hash of
-
# strings for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, <tt>:second</tt>
-
# will extend the select type with the given value. Use +html_options+ to modify every select tag in the set.
-
# * <tt>:use_hidden</tt> - Set to true if you only want to generate hidden input tags.
-
#
-
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
-
#
-
# NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute.
-
# date_select("article", "written_on")
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
-
# # with the year in the year drop down box starting at 1995.
-
# date_select("article", "written_on", start_year: 1995)
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
-
# # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
-
# # and without a day select box.
-
# date_select("article", "written_on", start_year: 1995, use_month_numbers: true,
-
# discard_day: true, include_blank: true)
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
-
# # with two digit numbers used for months and days.
-
# date_select("article", "written_on", use_two_digit_numbers: true)
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
-
# # with the fields ordered as day, month, year rather than month, day, year.
-
# date_select("article", "written_on", order: [:day, :month, :year])
-
#
-
# # Generates a date select that when POSTed is stored in the user variable, in the birthday attribute
-
# # lacking a year field.
-
# date_select("user", "birthday", order: [:month, :day])
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
-
# # which is initially set to the date 3 days from the current date
-
# date_select("article", "written_on", default: 3.days.from_now)
-
#
-
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
-
# # which is set in the form with today's date, regardless of the value in the Active Record object.
-
# date_select("article", "written_on", selected: Date.today)
-
#
-
# # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
-
# # that will have a default day of 20.
-
# date_select("credit_card", "bill_due", default: { day: 20 })
-
#
-
# # Generates a date select with custom prompts.
-
# date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' })
-
#
-
# # Generates a date select with custom year format.
-
# date_select("article", "written_on", year_format: ->(year) { "Heisei #{year - 1988}" })
-
#
-
# The selects are prepared for multi-parameter assignment to an Active Record object.
-
#
-
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
-
# all month choices are valid.
-
9
def date_select(object_name, method, options = {}, html_options = {})
-
Tags::DateSelect.new(object_name, method, self, options, html_options).render
-
end
-
-
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
-
# specified time-based attribute (identified by +method+) on an object assigned to the template (identified by
-
# +object+). You can include the seconds with <tt>:include_seconds</tt>. You can get hours in the AM/PM format
-
# with <tt>:ampm</tt> option.
-
#
-
# This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
-
# <tt>:ignore_date</tt> is set to +true+. If you set the <tt>:ignore_date</tt> to +true+, you must have a
-
# +date_select+ on the same method within the form otherwise an exception will be raised.
-
#
-
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
-
#
-
# # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute.
-
# time_select("article", "sunrise")
-
#
-
# # Creates a time select tag with a seconds field that, when POSTed, will be stored in the article variables in
-
# # the sunrise attribute.
-
# time_select("article", "start_time", include_seconds: true)
-
#
-
# # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45.
-
# time_select 'game', 'game_time', { minute_step: 15 }
-
#
-
# # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
-
# time_select("article", "written_on", prompt: { hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds' })
-
# time_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
-
# time_select("article", "written_on", prompt: true) # generic prompts for all
-
#
-
# # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
-
# time_select 'game', 'game_time', { ampm: true }
-
#
-
# The selects are prepared for multi-parameter assignment to an Active Record object.
-
#
-
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
-
# all month choices are valid.
-
9
def time_select(object_name, method, options = {}, html_options = {})
-
Tags::TimeSelect.new(object_name, method, self, options, html_options).render
-
end
-
-
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
-
# specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified
-
# by +object+).
-
#
-
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
-
#
-
# # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on
-
# # attribute.
-
# datetime_select("article", "written_on")
-
#
-
# # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
-
# # article variable in the written_on attribute.
-
# datetime_select("article", "written_on", start_year: 1995)
-
#
-
# # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will
-
# # be stored in the trip variable in the departing attribute.
-
# datetime_select("trip", "departing", default: 3.days.from_now)
-
#
-
# # Generate a datetime select with hours in the AM/PM format
-
# datetime_select("article", "written_on", ampm: true)
-
#
-
# # Generates a datetime select that discards the type that, when POSTed, will be stored in the article variable
-
# # as the written_on attribute.
-
# datetime_select("article", "written_on", discard_type: true)
-
#
-
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
-
# datetime_select("article", "written_on", prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
-
# datetime_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
-
# datetime_select("article", "written_on", prompt: true) # generic prompts for all
-
#
-
# The selects are prepared for multi-parameter assignment to an Active Record object.
-
9
def datetime_select(object_name, method, options = {}, html_options = {})
-
Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render
-
end
-
-
# Returns a set of HTML select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
-
# +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with
-
# an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not
-
# supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add
-
# <tt>:date_separator</tt>, <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to
-
# control visual display of the elements.
-
#
-
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
-
#
-
# my_date_time = Time.now + 4.days
-
#
-
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today).
-
# select_datetime(my_date_time)
-
#
-
# # Generates a datetime select that defaults to today (no specified datetime)
-
# select_datetime()
-
#
-
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
-
# # with the fields ordered year, month, day rather than month, day, year.
-
# select_datetime(my_date_time, order: [:year, :month, :day])
-
#
-
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
-
# # with a '/' between each date field.
-
# select_datetime(my_date_time, date_separator: '/')
-
#
-
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
-
# # with a date fields separated by '/', time fields separated by '' and the date and time fields
-
# # separated by a comma (',').
-
# select_datetime(my_date_time, date_separator: '/', time_separator: '', datetime_separator: ',')
-
#
-
# # Generates a datetime select that discards the type of the field and defaults to the datetime in
-
# # my_date_time (four days after today)
-
# select_datetime(my_date_time, discard_type: true)
-
#
-
# # Generate a datetime field with hours in the AM/PM format
-
# select_datetime(my_date_time, ampm: true)
-
#
-
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
-
# # prefixed with 'payday' rather than 'date'
-
# select_datetime(my_date_time, prefix: 'payday')
-
#
-
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
-
# select_datetime(my_date_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
-
# select_datetime(my_date_time, prompt: { hour: true }) # generic prompt for hours
-
# select_datetime(my_date_time, prompt: true) # generic prompts for all
-
9
def select_datetime(datetime = Time.current, options = {}, html_options = {})
-
DateTimeSelector.new(datetime, options, html_options).select_datetime
-
end
-
-
# Returns a set of HTML select-tags (one for year, month, and day) pre-selected with the +date+.
-
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
-
# symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order.
-
# If the array passed to the <tt>:order</tt> option does not contain all the three symbols, all tags will be hidden.
-
#
-
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
-
#
-
# my_date = Time.now + 6.days
-
#
-
# # Generates a date select that defaults to the date in my_date (six days after today).
-
# select_date(my_date)
-
#
-
# # Generates a date select that defaults to today (no specified date).
-
# select_date()
-
#
-
# # Generates a date select that defaults to the date in my_date (six days after today)
-
# # with the fields ordered year, month, day rather than month, day, year.
-
# select_date(my_date, order: [:year, :month, :day])
-
#
-
# # Generates a date select that discards the type of the field and defaults to the date in
-
# # my_date (six days after today).
-
# select_date(my_date, discard_type: true)
-
#
-
# # Generates a date select that defaults to the date in my_date,
-
# # which has fields separated by '/'.
-
# select_date(my_date, date_separator: '/')
-
#
-
# # Generates a date select that defaults to the datetime in my_date (six days after today)
-
# # prefixed with 'payday' rather than 'date'.
-
# select_date(my_date, prefix: 'payday')
-
#
-
# # Generates a date select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
-
# select_date(my_date, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
-
# select_date(my_date, prompt: { hour: true }) # generic prompt for hours
-
# select_date(my_date, prompt: true) # generic prompts for all
-
9
def select_date(date = Date.current, options = {}, html_options = {})
-
DateTimeSelector.new(date, options, html_options).select_date
-
end
-
-
# Returns a set of HTML select-tags (one for hour and minute).
-
# You can set <tt>:time_separator</tt> key to format the output, and
-
# the <tt>:include_seconds</tt> option to include an input for seconds.
-
#
-
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
-
#
-
# my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds
-
#
-
# # Generates a time select that defaults to the time in my_time.
-
# select_time(my_time)
-
#
-
# # Generates a time select that defaults to the current time (no specified time).
-
# select_time()
-
#
-
# # Generates a time select that defaults to the time in my_time,
-
# # which has fields separated by ':'.
-
# select_time(my_time, time_separator: ':')
-
#
-
# # Generates a time select that defaults to the time in my_time,
-
# # that also includes an input for seconds.
-
# select_time(my_time, include_seconds: true)
-
#
-
# # Generates a time select that defaults to the time in my_time, that has fields
-
# # separated by ':' and includes an input for seconds.
-
# select_time(my_time, time_separator: ':', include_seconds: true)
-
#
-
# # Generate a time select field with hours in the AM/PM format
-
# select_time(my_time, ampm: true)
-
#
-
# # Generates a time select field with hours that range from 2 to 14
-
# select_time(my_time, start_hour: 2, end_hour: 14)
-
#
-
# # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts.
-
# select_time(my_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
-
# select_time(my_time, prompt: { hour: true }) # generic prompt for hours
-
# select_time(my_time, prompt: true) # generic prompts for all
-
9
def select_time(datetime = Time.current, options = {}, html_options = {})
-
DateTimeSelector.new(datetime, options, html_options).select_time
-
end
-
-
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
-
# The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
-
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
-
#
-
# my_time = Time.now + 16.seconds
-
#
-
# # Generates a select field for seconds that defaults to the seconds for the time in my_time.
-
# select_second(my_time)
-
#
-
# # Generates a select field for seconds that defaults to the number given.
-
# select_second(33)
-
#
-
# # Generates a select field for seconds that defaults to the seconds for the time in my_time
-
# # that is named 'interval' rather than 'second'.
-
# select_second(my_time, field_name: 'interval')
-
#
-
# # Generates a select field for seconds with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_second(14, prompt: 'Choose seconds')
-
9
def select_second(datetime, options = {}, html_options = {})
-
DateTimeSelector.new(datetime, options, html_options).select_second
-
end
-
-
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
-
# Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute
-
# selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
-
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
-
#
-
# my_time = Time.now + 10.minutes
-
#
-
# # Generates a select field for minutes that defaults to the minutes for the time in my_time.
-
# select_minute(my_time)
-
#
-
# # Generates a select field for minutes that defaults to the number given.
-
# select_minute(14)
-
#
-
# # Generates a select field for minutes that defaults to the minutes for the time in my_time
-
# # that is named 'moment' rather than 'minute'.
-
# select_minute(my_time, field_name: 'moment')
-
#
-
# # Generates a select field for minutes with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_minute(14, prompt: 'Choose minutes')
-
9
def select_minute(datetime, options = {}, html_options = {})
-
DateTimeSelector.new(datetime, options, html_options).select_minute
-
end
-
-
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
-
# The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
-
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
-
#
-
# my_time = Time.now + 6.hours
-
#
-
# # Generates a select field for hours that defaults to the hour for the time in my_time.
-
# select_hour(my_time)
-
#
-
# # Generates a select field for hours that defaults to the number given.
-
# select_hour(13)
-
#
-
# # Generates a select field for hours that defaults to the hour for the time in my_time
-
# # that is named 'stride' rather than 'hour'.
-
# select_hour(my_time, field_name: 'stride')
-
#
-
# # Generates a select field for hours with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_hour(13, prompt: 'Choose hour')
-
#
-
# # Generate a select field for hours in the AM/PM format
-
# select_hour(my_time, ampm: true)
-
#
-
# # Generates a select field that includes options for hours from 2 to 14.
-
# select_hour(my_time, start_hour: 2, end_hour: 14)
-
9
def select_hour(datetime, options = {}, html_options = {})
-
DateTimeSelector.new(datetime, options, html_options).select_hour
-
end
-
-
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
-
# The <tt>date</tt> can also be substituted for a day number.
-
# If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
-
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
-
#
-
# my_date = Time.now + 2.days
-
#
-
# # Generates a select field for days that defaults to the day for the date in my_date.
-
# select_day(my_date)
-
#
-
# # Generates a select field for days that defaults to the number given.
-
# select_day(5)
-
#
-
# # Generates a select field for days that defaults to the number given, but displays it with two digits.
-
# select_day(5, use_two_digit_numbers: true)
-
#
-
# # Generates a select field for days that defaults to the day for the date in my_date
-
# # that is named 'due' rather than 'day'.
-
# select_day(my_date, field_name: 'due')
-
#
-
# # Generates a select field for days with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_day(5, prompt: 'Choose day')
-
9
def select_day(date, options = {}, html_options = {})
-
DateTimeSelector.new(date, options, html_options).select_day
-
end
-
-
# Returns a select tag with options for each of the months January through December with the current month
-
# selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are
-
# used as values (what's submitted to the server). It's also possible to use month numbers for the presentation
-
# instead of names -- set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you
-
# want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer
-
# to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want
-
# to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
-
# If you want to display months with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
-
# Override the field name using the <tt>:field_name</tt> option, 'month' by default.
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys like "January", "March".
-
# select_month(Date.today)
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # is named "start" rather than "month".
-
# select_month(Date.today, field_name: 'start')
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys like "1", "3".
-
# select_month(Date.today, use_month_numbers: true)
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys like "1 - January", "3 - March".
-
# select_month(Date.today, add_month_numbers: true)
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys like "Jan", "Mar".
-
# select_month(Date.today, use_short_month: true)
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys like "Januar", "Marts."
-
# select_month(Date.today, use_month_names: %w(Januar Februar Marts ...))
-
#
-
# # Generates a select field for months that defaults to the current month that
-
# # will use keys with two digit numbers like "01", "03".
-
# select_month(Date.today, use_two_digit_numbers: true)
-
#
-
# # Generates a select field for months with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_month(14, prompt: 'Choose month')
-
9
def select_month(date, options = {}, html_options = {})
-
DateTimeSelector.new(date, options, html_options).select_month
-
end
-
-
# Returns a select tag with options for each of the five years on each side of the current, which is selected.
-
# The five year radius can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the
-
# +options+. Both ascending and descending year lists are supported by making <tt>:start_year</tt> less than or
-
# greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number.
-
# Override the field name using the <tt>:field_name</tt> option, 'year' by default.
-
#
-
# # Generates a select field for years that defaults to the current year that
-
# # has ascending year values.
-
# select_year(Date.today, start_year: 1992, end_year: 2007)
-
#
-
# # Generates a select field for years that defaults to the current year that
-
# # is named 'birth' rather than 'year'.
-
# select_year(Date.today, field_name: 'birth')
-
#
-
# # Generates a select field for years that defaults to the current year that
-
# # has descending year values.
-
# select_year(Date.today, start_year: 2005, end_year: 1900)
-
#
-
# # Generates a select field for years that defaults to the year 2006 that
-
# # has ascending year values.
-
# select_year(2006, start_year: 2000, end_year: 2010)
-
#
-
# # Generates a select field for years with a custom prompt. Use <tt>prompt: true</tt> for a
-
# # generic prompt.
-
# select_year(14, prompt: 'Choose year')
-
9
def select_year(date, options = {}, html_options = {})
-
DateTimeSelector.new(date, options, html_options).select_year
-
end
-
-
# Returns an HTML time tag for the given date or time.
-
#
-
# time_tag Date.today # =>
-
# <time datetime="2010-11-04">November 04, 2010</time>
-
# time_tag Time.now # =>
-
# <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
-
# time_tag Date.yesterday, 'Yesterday' # =>
-
# <time datetime="2010-11-03">Yesterday</time>
-
# time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # =>
-
# <time datetime="2010-W44">November 04, 2010</time>
-
#
-
# <%= time_tag Time.now do %>
-
# <span>Right now</span>
-
# <% end %>
-
# # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time>
-
9
def time_tag(date_or_time, *args, &block)
-
options = args.extract_options!
-
format = options.delete(:format) || :long
-
content = args.first || I18n.l(date_or_time, format: format)
-
-
content_tag("time", content, options.reverse_merge(datetime: date_or_time.iso8601), &block)
-
end
-
-
9
private
-
9
def normalize_distance_of_time_argument_to_time(value)
-
if value.is_a?(Numeric)
-
Time.at(value)
-
elsif value.respond_to?(:to_time)
-
value.to_time
-
else
-
raise ArgumentError, "#{value.inspect} can't be converted to a Time value"
-
end
-
end
-
end
-
-
9
class DateTimeSelector #:nodoc:
-
9
include ActionView::Helpers::TagHelper
-
-
9
DEFAULT_PREFIX = "date"
-
9
POSITION = {
-
year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6
-
}.freeze
-
-
9
AMPM_TRANSLATION = Hash[
-
[[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"],
-
[4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"],
-
[8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"],
-
[12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"],
-
[16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"],
-
[20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]]
-
].freeze
-
-
9
def initialize(datetime, options = {}, html_options = {})
-
@options = options.dup
-
@html_options = html_options.dup
-
@datetime = datetime
-
@options[:datetime_separator] ||= " — "
-
@options[:time_separator] ||= " : "
-
end
-
-
9
def select_datetime
-
order = date_order.dup
-
order -= [:hour, :minute, :second]
-
@options[:discard_year] ||= true unless order.include?(:year)
-
@options[:discard_month] ||= true unless order.include?(:month)
-
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
-
@options[:discard_minute] ||= true if @options[:discard_hour]
-
@options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
-
-
set_day_if_discarded
-
-
if @options[:tag] && @options[:ignore_date]
-
select_time
-
else
-
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
-
order += [:hour, :minute, :second] unless @options[:discard_hour]
-
-
build_selects_from_types(order)
-
end
-
end
-
-
9
def select_date
-
order = date_order.dup
-
-
@options[:discard_hour] = true
-
@options[:discard_minute] = true
-
@options[:discard_second] = true
-
-
@options[:discard_year] ||= true unless order.include?(:year)
-
@options[:discard_month] ||= true unless order.include?(:month)
-
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
-
-
set_day_if_discarded
-
-
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
-
-
build_selects_from_types(order)
-
end
-
-
9
def select_time
-
order = []
-
-
@options[:discard_month] = true
-
@options[:discard_year] = true
-
@options[:discard_day] = true
-
@options[:discard_second] ||= true unless @options[:include_seconds]
-
-
order += [:year, :month, :day] unless @options[:ignore_date]
-
-
order += [:hour, :minute]
-
order << :second if @options[:include_seconds]
-
-
build_selects_from_types(order)
-
end
-
-
9
def select_second
-
if @options[:use_hidden] || @options[:discard_second]
-
build_hidden(:second, sec) if @options[:include_seconds]
-
else
-
build_options_and_select(:second, sec)
-
end
-
end
-
-
9
def select_minute
-
if @options[:use_hidden] || @options[:discard_minute]
-
build_hidden(:minute, min)
-
else
-
build_options_and_select(:minute, min, step: @options[:minute_step])
-
end
-
end
-
-
9
def select_hour
-
if @options[:use_hidden] || @options[:discard_hour]
-
build_hidden(:hour, hour)
-
else
-
options = {}
-
options[:ampm] = @options[:ampm] || false
-
options[:start] = @options[:start_hour] || 0
-
options[:end] = @options[:end_hour] || 23
-
build_options_and_select(:hour, hour, options)
-
end
-
end
-
-
9
def select_day
-
if @options[:use_hidden] || @options[:discard_day]
-
build_hidden(:day, day || 1)
-
else
-
build_options_and_select(:day, day, start: 1, end: 31, leading_zeros: false, use_two_digit_numbers: @options[:use_two_digit_numbers])
-
end
-
end
-
-
9
def select_month
-
if @options[:use_hidden] || @options[:discard_month]
-
build_hidden(:month, month || 1)
-
else
-
month_options = []
-
1.upto(12) do |month_number|
-
options = { value: month_number }
-
options[:selected] = "selected" if month == month_number
-
month_options << content_tag("option", month_name(month_number), options) + "\n"
-
end
-
build_select(:month, month_options.join)
-
end
-
end
-
-
9
def select_year
-
if !year || @datetime == 0
-
val = "1"
-
middle_year = Date.today.year
-
else
-
val = middle_year = year
-
end
-
-
if @options[:use_hidden] || @options[:discard_year]
-
build_hidden(:year, val)
-
else
-
options = {}
-
options[:start] = @options[:start_year] || middle_year - 5
-
options[:end] = @options[:end_year] || middle_year + 5
-
options[:step] = options[:start] < options[:end] ? 1 : -1
-
options[:leading_zeros] = false
-
options[:max_years_allowed] = @options[:max_years_allowed] || 1000
-
-
if (options[:end] - options[:start]).abs > options[:max_years_allowed]
-
raise ArgumentError, "There are too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter."
-
end
-
-
build_select(:year, build_year_options(val, options))
-
end
-
end
-
-
9
private
-
9
%w( sec min hour day month year ).each do |method|
-
54
define_method(method) do
-
case @datetime
-
when Hash then @datetime[method.to_sym]
-
when Numeric then @datetime
-
when nil then nil
-
else @datetime.send(method)
-
end
-
end
-
end
-
-
# If the day is hidden, the day should be set to the 1st so all month and year choices are
-
# valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid.
-
9
def set_day_if_discarded
-
if @datetime && @options[:discard_day]
-
@datetime = @datetime.change(day: 1)
-
end
-
end
-
-
# Returns translated month names, but also ensures that a custom month
-
# name array has a leading +nil+ element.
-
9
def month_names
-
@month_names ||= begin
-
month_names = @options[:use_month_names] || translated_month_names
-
month_names.unshift(nil) if month_names.size < 13
-
month_names
-
end
-
end
-
-
# Returns translated month names.
-
# => [nil, "January", "February", "March",
-
# "April", "May", "June", "July",
-
# "August", "September", "October",
-
# "November", "December"]
-
#
-
# If <tt>:use_short_month</tt> option is set
-
# => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-
# "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
-
9
def translated_month_names
-
key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
-
I18n.translate(key, locale: @options[:locale])
-
end
-
-
# Looks up month names by number (1-based):
-
#
-
# month_name(1) # => "January"
-
#
-
# If the <tt>:use_month_numbers</tt> option is passed:
-
#
-
# month_name(1) # => 1
-
#
-
# If the <tt>:use_two_month_numbers</tt> option is passed:
-
#
-
# month_name(1) # => '01'
-
#
-
# If the <tt>:add_month_numbers</tt> option is passed:
-
#
-
# month_name(1) # => "1 - January"
-
#
-
# If the <tt>:month_format_string</tt> option is passed:
-
#
-
# month_name(1) # => "January (01)"
-
#
-
# depending on the format string.
-
9
def month_name(number)
-
if @options[:use_month_numbers]
-
number
-
elsif @options[:use_two_digit_numbers]
-
"%02d" % number
-
elsif @options[:add_month_numbers]
-
"#{number} - #{month_names[number]}"
-
elsif format_string = @options[:month_format_string]
-
format_string % { number: number, name: month_names[number] }
-
else
-
month_names[number]
-
end
-
end
-
-
# Looks up year names by number.
-
#
-
# year_name(1998) # => 1998
-
#
-
# If the <tt>:year_format</tt> option is passed:
-
#
-
# year_name(1998) # => "Heisei 10"
-
9
def year_name(number)
-
if year_format_lambda = @options[:year_format]
-
year_format_lambda.call(number)
-
else
-
number
-
end
-
end
-
-
9
def date_order
-
@date_order ||= @options[:order] || translated_date_order
-
end
-
-
9
def translated_date_order
-
date_order = I18n.translate(:'date.order', locale: @options[:locale], default: [])
-
date_order = date_order.map(&:to_sym)
-
-
forbidden_elements = date_order - [:year, :month, :day]
-
if forbidden_elements.any?
-
raise StandardError,
-
"#{@options[:locale]}.date.order only accepts :year, :month and :day"
-
end
-
-
date_order
-
end
-
-
# Build full select tag from date type and options.
-
9
def build_options_and_select(type, selected, options = {})
-
build_select(type, build_options(selected, options))
-
end
-
-
# Build select option HTML from date value and options.
-
# build_options(15, start: 1, end: 31)
-
# => "<option value="1">1</option>
-
# <option value="2">2</option>
-
# <option value="3">3</option>..."
-
#
-
# If <tt>use_two_digit_numbers: true</tt> option is passed
-
# build_options(15, start: 1, end: 31, use_two_digit_numbers: true)
-
# => "<option value="1">01</option>
-
# <option value="2">02</option>
-
# <option value="3">03</option>..."
-
#
-
# If <tt>:step</tt> options is passed
-
# build_options(15, start: 1, end: 31, step: 2)
-
# => "<option value="1">1</option>
-
# <option value="3">3</option>
-
# <option value="5">5</option>..."
-
9
def build_options(selected, options = {})
-
options = {
-
leading_zeros: true, ampm: false, use_two_digit_numbers: false
-
}.merge!(options)
-
-
start = options.delete(:start) || 0
-
stop = options.delete(:end) || 59
-
step = options.delete(:step) || 1
-
leading_zeros = options.delete(:leading_zeros)
-
-
select_options = []
-
start.step(stop, step) do |i|
-
value = leading_zeros ? sprintf("%02d", i) : i
-
tag_options = { value: value }
-
tag_options[:selected] = "selected" if selected == i
-
text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
-
text = options[:ampm] ? AMPM_TRANSLATION[i] : text
-
select_options << content_tag("option", text, tag_options)
-
end
-
-
(select_options.join("\n") + "\n").html_safe
-
end
-
-
# Build select option HTML for year.
-
# If <tt>year_format</tt> option is not passed
-
# build_year_options(1998, start: 1998, end: 2000)
-
# => "<option value="1998" selected="selected">1998</option>
-
# <option value="1999">1999</option>
-
# <option value="2000">2000</option>"
-
#
-
# If <tt>year_format</tt> option is passed
-
# build_year_options(1998, start: 1998, end: 2000, year_format: ->year { "Heisei #{ year - 1988 }" })
-
# => "<option value="1998" selected="selected">Heisei 10</option>
-
# <option value="1999">Heisei 11</option>
-
# <option value="2000">Heisei 12</option>"
-
9
def build_year_options(selected, options = {})
-
start = options.delete(:start)
-
stop = options.delete(:end)
-
step = options.delete(:step)
-
-
select_options = []
-
start.step(stop, step) do |value|
-
tag_options = { value: value }
-
tag_options[:selected] = "selected" if selected == value
-
text = year_name(value)
-
select_options << content_tag("option", text, tag_options)
-
end
-
-
(select_options.join("\n") + "\n").html_safe
-
end
-
-
# Builds select tag from date type and HTML select options.
-
# build_select(:month, "<option value="1">January</option>...")
-
# => "<select id="post_written_on_2i" name="post[written_on(2i)]">
-
# <option value="1">January</option>...
-
# </select>"
-
9
def build_select(type, select_options_as_html)
-
select_options = {
-
id: input_id_from_type(type),
-
name: input_name_from_type(type)
-
}.merge!(@html_options)
-
select_options[:disabled] = "disabled" if @options[:disabled]
-
select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes]
-
-
select_html = +"\n"
-
select_html << content_tag("option", "", value: "", label: " ") + "\n" if @options[:include_blank]
-
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
-
select_html << select_options_as_html
-
-
(content_tag("select", select_html.html_safe, select_options) + "\n").html_safe
-
end
-
-
# Builds the css class value for the select element
-
# css_class_attribute(:year, 'date optional', { year: 'my-year' })
-
# => "date optional my-year"
-
9
def css_class_attribute(type, html_options_class, options) # :nodoc:
-
css_class = \
-
case options
-
when Hash
-
options[type.to_sym]
-
else
-
type
-
end
-
-
[html_options_class, css_class].compact.join(" ")
-
end
-
-
# Builds a prompt option tag with supplied options or from default options.
-
# prompt_option_tag(:month, prompt: 'Select month')
-
# => "<option value="">Select month</option>"
-
9
def prompt_option_tag(type, options)
-
prompt = \
-
case options
-
when Hash
-
default_options = { year: false, month: false, day: false, hour: false, minute: false, second: false }
-
default_options.merge!(options)[type.to_sym]
-
when String
-
options
-
else
-
I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale])
-
end
-
-
prompt ? content_tag("option", prompt, value: "") : ""
-
end
-
-
# Builds hidden input tag for date part and value.
-
# build_hidden(:year, 2008)
-
# => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
-
9
def build_hidden(type, value)
-
select_options = {
-
type: "hidden",
-
id: input_id_from_type(type),
-
name: input_name_from_type(type),
-
value: value
-
}.merge!(@html_options.slice(:disabled))
-
select_options[:disabled] = "disabled" if @options[:disabled]
-
-
tag(:input, select_options) + "\n".html_safe
-
end
-
-
# Returns the name attribute for the input tag.
-
# => post[written_on(1i)]
-
9
def input_name_from_type(type)
-
prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
-
prefix += "[#{@options[:index]}]" if @options.has_key?(:index)
-
-
field_name = @options[:field_name] || type.to_s
-
if @options[:include_position]
-
field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)"
-
end
-
-
@options[:discard_type] ? prefix : "#{prefix}[#{field_name}]"
-
end
-
-
# Returns the id attribute for the input tag.
-
# => "post_written_on_1i"
-
9
def input_id_from_type(type)
-
id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, "_").gsub(/[\]\)]/, "")
-
id = @options[:namespace] + "_" + id if @options[:namespace]
-
-
id
-
end
-
-
# Given an ordering of datetime components, create the selection HTML
-
# and join them with their appropriate separators.
-
9
def build_selects_from_types(order)
-
select = +""
-
first_visible = order.find { |type| !@options[:"discard_#{type}"] }
-
order.reverse_each do |type|
-
separator = separator(type) unless type == first_visible # don't add before first visible field
-
select.insert(0, separator.to_s + send("select_#{type}").to_s)
-
end
-
select.html_safe
-
end
-
-
# Returns the separator for a given datetime component.
-
9
def separator(type)
-
return "" if @options[:use_hidden]
-
-
case type
-
when :year, :month, :day
-
@options[:"discard_#{type}"] ? "" : @options[:date_separator]
-
when :hour
-
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
-
when :minute, :second
-
@options[:"discard_#{type}"] ? "" : @options[:time_separator]
-
end
-
end
-
end
-
-
9
class FormBuilder
-
# Wraps ActionView::Helpers::DateHelper#date_select for form builders:
-
#
-
# <%= form_for @person do |f| %>
-
# <%= f.date_select :birth_date %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
9
def date_select(method, options = {}, html_options = {})
-
@template.date_select(@object_name, method, objectify_options(options), html_options)
-
end
-
-
# Wraps ActionView::Helpers::DateHelper#time_select for form builders:
-
#
-
# <%= form_for @race do |f| %>
-
# <%= f.time_select :average_lap %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
9
def time_select(method, options = {}, html_options = {})
-
@template.time_select(@object_name, method, objectify_options(options), html_options)
-
end
-
-
# Wraps ActionView::Helpers::DateHelper#datetime_select for form builders:
-
#
-
# <%= form_for @person do |f| %>
-
# <%= f.datetime_select :last_request_at %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
9
def datetime_select(method, options = {}, html_options = {})
-
@template.datetime_select(@object_name, method, objectify_options(options), html_options)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
# = Action View Debug Helper
-
#
-
# Provides a set of methods for making it easier to debug Rails objects.
-
9
module Helpers #:nodoc:
-
9
module DebugHelper
-
9
include TagHelper
-
-
# Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
-
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
-
# Useful for inspecting an object at the time of rendering.
-
#
-
# @user = User.new({ username: 'testing', password: 'xyz', age: 42})
-
# debug(@user)
-
# # =>
-
# <pre class='debug_dump'>--- !ruby/object:User
-
# attributes:
-
# updated_at:
-
# username: testing
-
# age: 42
-
# password: xyz
-
# created_at:
-
# </pre>
-
9
def debug(object)
-
Marshal.dump(object)
-
object = ERB::Util.html_escape(object.to_yaml)
-
content_tag(:pre, object, class: "debug_dump")
-
rescue # errors from Marshal or YAML
-
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
-
content_tag(:code, object.inspect, class: "debug_dump")
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "cgi"
-
9
require "action_view/helpers/date_helper"
-
9
require "action_view/helpers/tag_helper"
-
9
require "action_view/helpers/form_tag_helper"
-
9
require "action_view/helpers/active_model_helper"
-
9
require "action_view/model_naming"
-
9
require "action_view/record_identifier"
-
9
require "active_support/core_ext/module/attribute_accessors"
-
9
require "active_support/core_ext/hash/slice"
-
9
require "active_support/core_ext/string/output_safety"
-
9
require "active_support/core_ext/string/inflections"
-
9
require "active_support/core_ext/symbol/starts_ends_with"
-
-
9
module ActionView
-
# = Action View Form Helpers
-
9
module Helpers #:nodoc:
-
# Form helpers are designed to make working with resources much easier
-
# compared to using vanilla HTML.
-
#
-
# Typically, a form designed to create or update a resource reflects the
-
# identity of the resource in several ways: (i) the URL that the form is
-
# sent to (the form element's +action+ attribute) should result in a request
-
# being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
-
# parameter in the case of an existing resource), (ii) input fields should
-
# be named in such a way that in the controller their values appear in the
-
# appropriate places within the +params+ hash, and (iii) for an existing record,
-
# when the form is initially displayed, input fields corresponding to attributes
-
# of the resource should show the current values of those attributes.
-
#
-
# In Rails, this is usually achieved by creating the form using +form_for+ and
-
# a number of related helper methods. +form_for+ generates an appropriate <tt>form</tt>
-
# tag and yields a form builder object that knows the model the form is about.
-
# Input fields are created by calling methods defined on the form builder, which
-
# means they are able to generate the appropriate names and default values
-
# corresponding to the model attributes, as well as convenient IDs, etc.
-
# Conventions in the generated field names allow controllers to receive form data
-
# nicely structured in +params+ with no effort on your side.
-
#
-
# For example, to create a new person you typically set up a new instance of
-
# +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and
-
# in the view template pass that object to +form_for+:
-
#
-
# <%= form_for @person do |f| %>
-
# <%= f.label :first_name %>:
-
# <%= f.text_field :first_name %><br />
-
#
-
# <%= f.label :last_name %>:
-
# <%= f.text_field :last_name %><br />
-
#
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# The HTML generated for this would be (modulus formatting):
-
#
-
# <form action="/people" class="new_person" id="new_person" method="post">
-
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
-
# <label for="person_first_name">First name</label>:
-
# <input id="person_first_name" name="person[first_name]" type="text" /><br />
-
#
-
# <label for="person_last_name">Last name</label>:
-
# <input id="person_last_name" name="person[last_name]" type="text" /><br />
-
#
-
# <input name="commit" type="submit" value="Create Person" />
-
# </form>
-
#
-
# As you see, the HTML reflects knowledge about the resource in several spots,
-
# like the path the form should be submitted to, or the names of the input fields.
-
#
-
# In particular, thanks to the conventions followed in the generated field names, the
-
# controller gets a nested hash <tt>params[:person]</tt> with the person attributes
-
# set in the form. That hash is ready to be passed to <tt>Person.new</tt>:
-
#
-
# @person = Person.new(params[:person])
-
# if @person.save
-
# # success
-
# else
-
# # error handling
-
# end
-
#
-
# Interestingly, the exact same view code in the previous example can be used to edit
-
# a person. If <tt>@person</tt> is an existing record with name "John Smith" and ID 256,
-
# the code above as is would yield instead:
-
#
-
# <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
-
# <input name="_method" type="hidden" value="patch" />
-
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
-
# <label for="person_first_name">First name</label>:
-
# <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br />
-
#
-
# <label for="person_last_name">Last name</label>:
-
# <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br />
-
#
-
# <input name="commit" type="submit" value="Update Person" />
-
# </form>
-
#
-
# Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>.
-
# That works that way because the involved helpers know whether the resource is a new record or not,
-
# and generate HTML accordingly.
-
#
-
# The controller would receive the form data again in <tt>params[:person]</tt>, ready to be
-
# passed to <tt>Person#update</tt>:
-
#
-
# if @person.update(params[:person])
-
# # success
-
# else
-
# # error handling
-
# end
-
#
-
# That's how you typically work with resources.
-
9
module FormHelper
-
9
extend ActiveSupport::Concern
-
-
9
include FormTagHelper
-
9
include UrlHelper
-
9
include ModelNaming
-
9
include RecordIdentifier
-
-
9
attr_internal :default_form_builder
-
-
# Creates a form that allows the user to create or update the attributes
-
# of a specific model object.
-
#
-
# The method can be used in several slightly different ways, depending on
-
# how much you wish to rely on Rails to infer automatically from the model
-
# how the form should be constructed. For a generic model object, a form
-
# can be created by passing +form_for+ a string or symbol representing
-
# the object we are concerned with:
-
#
-
# <%= form_for :person do |f| %>
-
# First name: <%= f.text_field :first_name %><br />
-
# Last name : <%= f.text_field :last_name %><br />
-
# Biography : <%= f.text_area :biography %><br />
-
# Admin? : <%= f.check_box :admin %><br />
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# The variable +f+ yielded to the block is a FormBuilder object that
-
# incorporates the knowledge about the model object represented by
-
# <tt>:person</tt> passed to +form_for+. Methods defined on the FormBuilder
-
# are used to generate fields bound to this model. Thus, for example,
-
#
-
# <%= f.text_field :first_name %>
-
#
-
# will get expanded to
-
#
-
# <%= text_field :person, :first_name %>
-
#
-
# which results in an HTML <tt><input></tt> tag whose +name+ attribute is
-
# <tt>person[first_name]</tt>. This means that when the form is submitted,
-
# the value entered by the user will be available in the controller as
-
# <tt>params[:person][:first_name]</tt>.
-
#
-
# For fields generated in this way using the FormBuilder,
-
# if <tt>:person</tt> also happens to be the name of an instance variable
-
# <tt>@person</tt>, the default value of the field shown when the form is
-
# initially displayed (e.g. in the situation where you are editing an
-
# existing record) will be the value of the corresponding attribute of
-
# <tt>@person</tt>.
-
#
-
# The rightmost argument to +form_for+ is an
-
# optional hash of options -
-
#
-
# * <tt>:url</tt> - The URL the form is to be submitted to. This may be
-
# represented in the same way as values passed to +url_for+ or +link_to+.
-
# So for example you may use a named route directly. When the model is
-
# represented by a string or symbol, as in the example above, if the
-
# <tt>:url</tt> option is not specified, by default the form will be
-
# sent back to the current URL (We will describe below an alternative
-
# resource-oriented usage of +form_for+ in which the URL does not need
-
# to be specified explicitly).
-
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
-
# id attributes on form elements. The namespace attribute will be prefixed
-
# with underscore on the generated HTML id.
-
# * <tt>:method</tt> - The method to use when submitting the form, usually
-
# either "get" or "post". If "patch", "put", "delete", or another verb
-
# is used, a hidden input with name <tt>_method</tt> is added to
-
# simulate the verb over post.
-
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
-
# Use only if you need to pass custom authenticity token string, or to
-
# not add authenticity_token field at all (by passing <tt>false</tt>).
-
# Remote forms may omit the embedded authenticity token by setting
-
# <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
-
# This is helpful when you're fragment-caching the form. Remote forms
-
# get the authenticity token from the <tt>meta</tt> tag, so embedding is
-
# unnecessary unless you support browsers without JavaScript.
-
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive
-
# JavaScript drivers to control the submit behavior. By default this
-
# behavior is an ajax submit.
-
# * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name
-
# utf8 is not output.
-
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
-
#
-
# Also note that +form_for+ doesn't create an exclusive scope. It's still
-
# possible to use both the stand-alone FormHelper methods and methods
-
# from FormTagHelper. For example:
-
#
-
# <%= form_for :person do |f| %>
-
# First name: <%= f.text_field :first_name %>
-
# Last name : <%= f.text_field :last_name %>
-
# Biography : <%= text_area :person, :biography %>
-
# Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# This also works for the methods in FormOptionsHelper and DateHelper that
-
# are designed to work with an object as base, like
-
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
-
#
-
# === #form_for with a model object
-
#
-
# In the examples above, the object to be created or edited was
-
# represented by a symbol passed to +form_for+, and we noted that
-
# a string can also be used equivalently. It is also possible, however,
-
# to pass a model object itself to +form_for+. For example, if <tt>@post</tt>
-
# is an existing record you wish to edit, you can create the form using
-
#
-
# <%= form_for @post do |f| %>
-
# ...
-
# <% end %>
-
#
-
# This behaves in almost the same way as outlined previously, with a
-
# couple of small exceptions. First, the prefix used to name the input
-
# elements within the form (hence the key that denotes them in the +params+
-
# hash) is actually derived from the object's _class_, e.g. <tt>params[:post]</tt>
-
# if the object's class is +Post+. However, this can be overwritten using
-
# the <tt>:as</tt> option, e.g. -
-
#
-
# <%= form_for(@person, as: :client) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# would result in <tt>params[:client]</tt>.
-
#
-
# Secondly, the field values shown when the form is initially displayed
-
# are taken from the attributes of the object passed to +form_for+,
-
# regardless of whether the object is an instance
-
# variable. So, for example, if we had a _local_ variable +post+
-
# representing an existing record,
-
#
-
# <%= form_for post do |f| %>
-
# ...
-
# <% end %>
-
#
-
# would produce a form with fields whose initial state reflect the current
-
# values of the attributes of +post+.
-
#
-
# === Resource-oriented style
-
#
-
# In the examples just shown, although not indicated explicitly, we still
-
# need to use the <tt>:url</tt> option in order to specify where the
-
# form is going to be sent. However, further simplification is possible
-
# if the record passed to +form_for+ is a _resource_, i.e. it corresponds
-
# to a set of RESTful routes, e.g. defined using the +resources+ method
-
# in <tt>config/routes.rb</tt>. In this case Rails will simply infer the
-
# appropriate URL from the record itself. For example,
-
#
-
# <%= form_for @post do |f| %>
-
# ...
-
# <% end %>
-
#
-
# is then equivalent to something like:
-
#
-
# <%= form_for @post, as: :post, url: post_path(@post), method: :patch, html: { class: "edit_post", id: "edit_post_45" } do |f| %>
-
# ...
-
# <% end %>
-
#
-
# And for a new record
-
#
-
# <%= form_for(Post.new) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# is equivalent to something like:
-
#
-
# <%= form_for @post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %>
-
# ...
-
# <% end %>
-
#
-
# However you can still overwrite individual conventions, such as:
-
#
-
# <%= form_for(@post, url: super_posts_path) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# You can also set the answer format, like this:
-
#
-
# <%= form_for(@post, format: :json) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# For namespaced routes, like +admin_post_url+:
-
#
-
# <%= form_for([:admin, @post]) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# If your resource has associations defined, for example, you want to add comments
-
# to the document given that the routes are set correctly:
-
#
-
# <%= form_for([@document, @comment]) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# Where <tt>@document = Document.find(params[:id])</tt> and
-
# <tt>@comment = Comment.new</tt>.
-
#
-
# === Setting the method
-
#
-
# You can force the form to use the full array of HTTP verbs by setting
-
#
-
# method: (:get|:post|:patch|:put|:delete)
-
#
-
# in the options hash. If the verb is not GET or POST, which are natively
-
# supported by HTML forms, the form will be set to POST and a hidden input
-
# called _method will carry the intended verb for the server to interpret.
-
#
-
# === Unobtrusive JavaScript
-
#
-
# Specifying:
-
#
-
# remote: true
-
#
-
# in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
-
# behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
-
# POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
-
# Even though it's using JavaScript to serialize the form elements, the form submission will work just like
-
# a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
-
#
-
# Example:
-
#
-
# <%= form_for(@post, remote: true) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# The HTML generated for this would be:
-
#
-
# <form action='http://www.example.com' method='post' data-remote='true'>
-
# <input name='_method' type='hidden' value='patch' />
-
# ...
-
# </form>
-
#
-
# === Setting HTML options
-
#
-
# You can set data attributes directly by passing in a data hash, but all other HTML options must be wrapped in
-
# the HTML key. Example:
-
#
-
# <%= form_for(@post, data: { behavior: "autosave" }, html: { name: "go" }) do |f| %>
-
# ...
-
# <% end %>
-
#
-
# The HTML generated for this would be:
-
#
-
# <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'>
-
# <input name='_method' type='hidden' value='patch' />
-
# ...
-
# </form>
-
#
-
# === Removing hidden model id's
-
#
-
# The form_for method automatically includes the model id as a hidden field in the form.
-
# This is used to maintain the correlation between the form data and its associated model.
-
# Some ORM systems do not use IDs on nested models so in this case you want to be able
-
# to disable the hidden id.
-
#
-
# In the following example the Post model has many Comments stored within it in a NoSQL database,
-
# thus there is no primary key for comments.
-
#
-
# Example:
-
#
-
# <%= form_for(@post) do |f| %>
-
# <%= f.fields_for(:comments, include_id: false) do |cf| %>
-
# ...
-
# <% end %>
-
# <% end %>
-
#
-
# === Customized form builders
-
#
-
# You can also build forms using a customized FormBuilder class. Subclass
-
# FormBuilder and override or define some more helpers, then use your
-
# custom builder. For example, let's say you made a helper to
-
# automatically add labels to form inputs.
-
#
-
# <%= form_for @person, url: { action: "create" }, builder: LabellingFormBuilder do |f| %>
-
# <%= f.text_field :first_name %>
-
# <%= f.text_field :last_name %>
-
# <%= f.text_area :biography %>
-
# <%= f.check_box :admin %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# In this case, if you use this:
-
#
-
# <%= render f %>
-
#
-
# The rendered template is <tt>people/_labelling_form</tt> and the local
-
# variable referencing the form builder is called
-
# <tt>labelling_form</tt>.
-
#
-
# The custom FormBuilder class is automatically merged with the options
-
# of a nested fields_for call, unless it's explicitly set.
-
#
-
# In many cases you will want to wrap the above in another helper, so you
-
# could do something like the following:
-
#
-
# def labelled_form_for(record_or_name_or_array, *args, &block)
-
# options = args.extract_options!
-
# form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &block)
-
# end
-
#
-
# If you don't need to attach a form to a model instance, then check out
-
# FormTagHelper#form_tag.
-
#
-
# === Form to external resources
-
#
-
# When you build forms to external resources sometimes you need to set an authenticity token or just render a form
-
# without it, for example when you submit data to a payment gateway number and types of fields could be limited.
-
#
-
# To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
-
#
-
# <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| %>
-
# ...
-
# <% end %>
-
#
-
# If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
-
#
-
# <%= form_for @invoice, url: external_url, authenticity_token: false do |f| %>
-
# ...
-
# <% end %>
-
9
def form_for(record, options = {}, &block)
-
raise ArgumentError, "Missing block" unless block_given?
-
html_options = options[:html] ||= {}
-
-
case record
-
when String, Symbol
-
object_name = record
-
object = nil
-
else
-
object = record.is_a?(Array) ? record.last : record
-
raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
-
object_name = options[:as] || model_name_from_record_or_class(object).param_key
-
apply_form_for_options!(record, object, options)
-
end
-
-
html_options[:data] = options.delete(:data) if options.has_key?(:data)
-
html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
-
html_options[:method] = options.delete(:method) if options.has_key?(:method)
-
html_options[:enforce_utf8] = options.delete(:enforce_utf8) if options.has_key?(:enforce_utf8)
-
html_options[:authenticity_token] = options.delete(:authenticity_token)
-
-
builder = instantiate_builder(object_name, object, options)
-
output = capture(builder, &block)
-
html_options[:multipart] ||= builder.multipart?
-
-
html_options = html_options_for_form(options[:url] || {}, html_options)
-
form_tag_with_body(html_options, output)
-
end
-
-
9
def apply_form_for_options!(record, object, options) #:nodoc:
-
object = convert_to_model(object)
-
-
as = options[:as]
-
namespace = options[:namespace]
-
action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
-
options[:html].reverse_merge!(
-
class: as ? "#{action}_#{as}" : dom_class(object, action),
-
id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence,
-
method: method
-
)
-
-
options[:url] ||= if options.key?(:format)
-
polymorphic_path(record, format: options.delete(:format))
-
else
-
polymorphic_path(record, {})
-
end
-
end
-
9
private :apply_form_for_options!
-
-
9
mattr_accessor :form_with_generates_remote_forms, default: true
-
-
9
mattr_accessor :form_with_generates_ids, default: false
-
-
# Creates a form tag based on mixing URLs, scopes, or models.
-
#
-
# # Using just a URL:
-
# <%= form_with url: posts_path do |form| %>
-
# <%= form.text_field :title %>
-
# <% end %>
-
# # =>
-
# <form action="/posts" method="post" data-remote="true">
-
# <input type="text" name="title">
-
# </form>
-
#
-
# # Adding a scope prefixes the input field names:
-
# <%= form_with scope: :post, url: posts_path do |form| %>
-
# <%= form.text_field :title %>
-
# <% end %>
-
# # =>
-
# <form action="/posts" method="post" data-remote="true">
-
# <input type="text" name="post[title]">
-
# </form>
-
#
-
# # Using a model infers both the URL and scope:
-
# <%= form_with model: Post.new do |form| %>
-
# <%= form.text_field :title %>
-
# <% end %>
-
# # =>
-
# <form action="/posts" method="post" data-remote="true">
-
# <input type="text" name="post[title]">
-
# </form>
-
#
-
# # An existing model makes an update form and fills out field values:
-
# <%= form_with model: Post.first do |form| %>
-
# <%= form.text_field :title %>
-
# <% end %>
-
# # =>
-
# <form action="/posts/1" method="post" data-remote="true">
-
# <input type="hidden" name="_method" value="patch">
-
# <input type="text" name="post[title]" value="<the title of the post>">
-
# </form>
-
#
-
# # Though the fields don't have to correspond to model attributes:
-
# <%= form_with model: Cat.new do |form| %>
-
# <%= form.text_field :cats_dont_have_gills %>
-
# <%= form.text_field :but_in_forms_they_can %>
-
# <% end %>
-
# # =>
-
# <form action="/cats" method="post" data-remote="true">
-
# <input type="text" name="cat[cats_dont_have_gills]">
-
# <input type="text" name="cat[but_in_forms_they_can]">
-
# </form>
-
#
-
# The parameters in the forms are accessible in controllers according to
-
# their name nesting. So inputs named +title+ and <tt>post[title]</tt> are
-
# accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt>
-
# respectively.
-
#
-
# By default +form_with+ attaches the <tt>data-remote</tt> attribute
-
# submitting the form via an XMLHTTPRequest in the background if an
-
# Unobtrusive JavaScript driver, like rails-ujs, is used. See the
-
# <tt>:local</tt> option for more.
-
#
-
# For ease of comparison the examples above left out the submit button,
-
# as well as the auto generated hidden fields that enable UTF-8 support
-
# and adds an authenticity token needed for cross site request forgery
-
# protection.
-
#
-
# === Resource-oriented style
-
#
-
# In many of the examples just shown, the +:model+ passed to +form_with+
-
# is a _resource_. It corresponds to a set of RESTful routes, most likely
-
# defined via +resources+ in <tt>config/routes.rb</tt>.
-
#
-
# So when passing such a model record, Rails infers the URL and method.
-
#
-
# <%= form_with model: @post do |form| %>
-
# ...
-
# <% end %>
-
#
-
# is then equivalent to something like:
-
#
-
# <%= form_with scope: :post, url: post_path(@post), method: :patch do |form| %>
-
# ...
-
# <% end %>
-
#
-
# And for a new record
-
#
-
# <%= form_with model: Post.new do |form| %>
-
# ...
-
# <% end %>
-
#
-
# is equivalent to something like:
-
#
-
# <%= form_with scope: :post, url: posts_path do |form| %>
-
# ...
-
# <% end %>
-
#
-
# ==== +form_with+ options
-
#
-
# * <tt>:url</tt> - The URL the form submits to. Akin to values passed to
-
# +url_for+ or +link_to+. For example, you may use a named route
-
# directly. When a <tt>:scope</tt> is passed without a <tt>:url</tt> the
-
# form just submits to the current URL.
-
# * <tt>:method</tt> - The method to use when submitting the form, usually
-
# either "get" or "post". If "patch", "put", "delete", or another verb
-
# is used, a hidden input named <tt>_method</tt> is added to
-
# simulate the verb over post.
-
# * <tt>:format</tt> - The format of the route the form submits to.
-
# Useful when submitting to another resource type, like <tt>:json</tt>.
-
# Skipped if a <tt>:url</tt> is passed.
-
# * <tt>:scope</tt> - The scope to prefix input field names with and
-
# thereby how the submitted parameters are grouped in controllers.
-
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
-
# id attributes on form elements. The namespace attribute will be prefixed
-
# with underscore on the generated HTML id.
-
# * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
-
# <tt>:scope</tt> by, plus fill out input field values.
-
# So if a +title+ attribute is set to "Ahoy!" then a +title+ input
-
# field's value would be "Ahoy!".
-
# If the model is a new record a create form is generated, if an
-
# existing record, however, an update form is generated.
-
# Pass <tt>:scope</tt> or <tt>:url</tt> to override the defaults.
-
# E.g. turn <tt>params[:post]</tt> into <tt>params[:article]</tt>.
-
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form.
-
# Override with a custom authenticity token or pass <tt>false</tt> to
-
# skip the authenticity token field altogether.
-
# Useful when submitting to an external resource like a payment gateway
-
# that might limit the valid fields.
-
# Remote forms may omit the embedded authenticity token by setting
-
# <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
-
# This is helpful when fragment-caching the form. Remote forms
-
# get the authenticity token from the <tt>meta</tt> tag, so embedding is
-
# unnecessary unless you support browsers without JavaScript.
-
# * <tt>:local</tt> - By default form submits are remote and unobtrusive XHRs.
-
# Disable remote submits with <tt>local: true</tt>.
-
# * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name
-
# utf8 is not output.
-
# * <tt>:builder</tt> - Override the object used to build the form.
-
# * <tt>:id</tt> - Optional HTML id attribute.
-
# * <tt>:class</tt> - Optional HTML class attribute.
-
# * <tt>:data</tt> - Optional HTML data attributes.
-
# * <tt>:html</tt> - Other optional HTML attributes for the form tag.
-
#
-
# === Examples
-
#
-
# When not passing a block, +form_with+ just generates an opening form tag.
-
#
-
# <%= form_with(model: @post, url: super_posts_path) %>
-
# <%= form_with(model: @post, scope: :article) %>
-
# <%= form_with(model: @post, format: :json) %>
-
# <%= form_with(model: @post, authenticity_token: false) %> # Disables the token.
-
#
-
# For namespaced routes, like +admin_post_url+:
-
#
-
# <%= form_with(model: [ :admin, @post ]) do |form| %>
-
# ...
-
# <% end %>
-
#
-
# If your resource has associations defined, for example, you want to add comments
-
# to the document given that the routes are set correctly:
-
#
-
# <%= form_with(model: [ @document, Comment.new ]) do |form| %>
-
# ...
-
# <% end %>
-
#
-
# Where <tt>@document = Document.find(params[:id])</tt>.
-
#
-
# === Mixing with other form helpers
-
#
-
# While +form_with+ uses a FormBuilder object it's possible to mix and
-
# match the stand-alone FormHelper methods and methods
-
# from FormTagHelper:
-
#
-
# <%= form_with scope: :person do |form| %>
-
# <%= form.text_field :first_name %>
-
# <%= form.text_field :last_name %>
-
#
-
# <%= text_area :person, :biography %>
-
# <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
-
#
-
# <%= form.submit %>
-
# <% end %>
-
#
-
# Same goes for the methods in FormOptionsHelper and DateHelper designed
-
# to work with an object as a base, like
-
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
-
#
-
# === Setting the method
-
#
-
# You can force the form to use the full array of HTTP verbs by setting
-
#
-
# method: (:get|:post|:patch|:put|:delete)
-
#
-
# in the options hash. If the verb is not GET or POST, which are natively
-
# supported by HTML forms, the form will be set to POST and a hidden input
-
# called _method will carry the intended verb for the server to interpret.
-
#
-
# === Setting HTML options
-
#
-
# You can set data attributes directly in a data hash, but HTML options
-
# besides id and class must be wrapped in an HTML key:
-
#
-
# <%= form_with(model: @post, data: { behavior: "autosave" }, html: { name: "go" }) do |form| %>
-
# ...
-
# <% end %>
-
#
-
# generates
-
#
-
# <form action="/posts/123" method="post" data-behavior="autosave" name="go">
-
# <input name="_method" type="hidden" value="patch" />
-
# ...
-
# </form>
-
#
-
# === Removing hidden model id's
-
#
-
# The +form_with+ method automatically includes the model id as a hidden field in the form.
-
# This is used to maintain the correlation between the form data and its associated model.
-
# Some ORM systems do not use IDs on nested models so in this case you want to be able
-
# to disable the hidden id.
-
#
-
# In the following example the Post model has many Comments stored within it in a NoSQL database,
-
# thus there is no primary key for comments.
-
#
-
# <%= form_with(model: @post) do |form| %>
-
# <%= form.fields(:comments, skip_id: true) do |fields| %>
-
# ...
-
# <% end %>
-
# <% end %>
-
#
-
# === Customized form builders
-
#
-
# You can also build forms using a customized FormBuilder class. Subclass
-
# FormBuilder and override or define some more helpers, then use your
-
# custom builder. For example, let's say you made a helper to
-
# automatically add labels to form inputs.
-
#
-
# <%= form_with model: @person, url: { action: "create" }, builder: LabellingFormBuilder do |form| %>
-
# <%= form.text_field :first_name %>
-
# <%= form.text_field :last_name %>
-
# <%= form.text_area :biography %>
-
# <%= form.check_box :admin %>
-
# <%= form.submit %>
-
# <% end %>
-
#
-
# In this case, if you use:
-
#
-
# <%= render form %>
-
#
-
# The rendered template is <tt>people/_labelling_form</tt> and the local
-
# variable referencing the form builder is called
-
# <tt>labelling_form</tt>.
-
#
-
# The custom FormBuilder class is automatically merged with the options
-
# of a nested +fields+ call, unless it's explicitly set.
-
#
-
# In many cases you will want to wrap the above in another helper, so you
-
# could do something like the following:
-
#
-
# def labelled_form_with(**options, &block)
-
# form_with(**options.merge(builder: LabellingFormBuilder), &block)
-
# end
-
9
def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
-
options[:allow_method_names_outside_object] = true
-
options[:skip_default_ids] = !form_with_generates_ids
-
-
if model
-
url ||= polymorphic_path(model, format: format)
-
-
model = model.last if model.is_a?(Array)
-
scope ||= model_name_from_record_or_class(model).param_key
-
end
-
-
if block_given?
-
builder = instantiate_builder(scope, model, options)
-
output = capture(builder, &block)
-
options[:multipart] ||= builder.multipart?
-
-
html_options = html_options_for_form_with(url, model, **options)
-
form_tag_with_body(html_options, output)
-
else
-
html_options = html_options_for_form_with(url, model, **options)
-
form_tag_html(html_options)
-
end
-
end
-
-
# Creates a scope around a specific model object like form_for, but
-
# doesn't create the form tags themselves. This makes fields_for suitable
-
# for specifying additional model objects in the same form.
-
#
-
# Although the usage and purpose of +fields_for+ is similar to +form_for+'s,
-
# its method signature is slightly different. Like +form_for+, it yields
-
# a FormBuilder object associated with a particular model object to a block,
-
# and within the block allows methods to be called on the builder to
-
# generate fields associated with the model object. Fields may reflect
-
# a model object in two ways - how they are named (hence how submitted
-
# values appear within the +params+ hash in the controller) and what
-
# default values are shown when the form the fields appear in is first
-
# displayed. In order for both of these features to be specified independently,
-
# both an object name (represented by either a symbol or string) and the
-
# object itself can be passed to the method separately -
-
#
-
# <%= form_for @person do |person_form| %>
-
# First name: <%= person_form.text_field :first_name %>
-
# Last name : <%= person_form.text_field :last_name %>
-
#
-
# <%= fields_for :permission, @person.permission do |permission_fields| %>
-
# Admin? : <%= permission_fields.check_box :admin %>
-
# <% end %>
-
#
-
# <%= person_form.submit %>
-
# <% end %>
-
#
-
# In this case, the checkbox field will be represented by an HTML +input+
-
# tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted
-
# value will appear in the controller as <tt>params[:permission][:admin]</tt>.
-
# If <tt>@person.permission</tt> is an existing record with an attribute
-
# +admin+, the initial state of the checkbox when first displayed will
-
# reflect the value of <tt>@person.permission.admin</tt>.
-
#
-
# Often this can be simplified by passing just the name of the model
-
# object to +fields_for+ -
-
#
-
# <%= fields_for :permission do |permission_fields| %>
-
# Admin?: <%= permission_fields.check_box :admin %>
-
# <% end %>
-
#
-
# ...in which case, if <tt>:permission</tt> also happens to be the name of an
-
# instance variable <tt>@permission</tt>, the initial state of the input
-
# field will reflect the value of that variable's attribute <tt>@permission.admin</tt>.
-
#
-
# Alternatively, you can pass just the model object itself (if the first
-
# argument isn't a string or symbol +fields_for+ will realize that the
-
# name has been omitted) -
-
#
-
# <%= fields_for @person.permission do |permission_fields| %>
-
# Admin?: <%= permission_fields.check_box :admin %>
-
# <% end %>
-
#
-
# and +fields_for+ will derive the required name of the field from the
-
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
-
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
-
#
-
# Note: This also works for the methods in FormOptionsHelper and
-
# DateHelper that are designed to work with an object as base, like
-
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
-
#
-
# === Nested Attributes Examples
-
#
-
# When the object belonging to the current scope has a nested attribute
-
# writer for a certain attribute, fields_for will yield a new scope
-
# for that attribute. This allows you to create forms that set or change
-
# the attributes of a parent object and its associations in one go.
-
#
-
# Nested attribute writers are normal setter methods named after an
-
# association. The most common way of defining these writers is either
-
# with +accepts_nested_attributes_for+ in a model definition or by
-
# defining a method with the proper name. For example: the attribute
-
# writer for the association <tt>:address</tt> is called
-
# <tt>address_attributes=</tt>.
-
#
-
# Whether a one-to-one or one-to-many style form builder will be yielded
-
# depends on whether the normal reader method returns a _single_ object
-
# or an _array_ of objects.
-
#
-
# ==== One-to-one
-
#
-
# Consider a Person class which returns a _single_ Address from the
-
# <tt>address</tt> reader method and responds to the
-
# <tt>address_attributes=</tt> writer method:
-
#
-
# class Person
-
# def address
-
# @address
-
# end
-
#
-
# def address_attributes=(attributes)
-
# # Process the attributes hash
-
# end
-
# end
-
#
-
# This model can now be used with a nested fields_for, like so:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :address do |address_fields| %>
-
# Street : <%= address_fields.text_field :street %>
-
# Zip code: <%= address_fields.text_field :zip_code %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# When address is already an association on a Person you can use
-
# +accepts_nested_attributes_for+ to define the writer method for you:
-
#
-
# class Person < ActiveRecord::Base
-
# has_one :address
-
# accepts_nested_attributes_for :address
-
# end
-
#
-
# If you want to destroy the associated model through the form, you have
-
# to enable it first using the <tt>:allow_destroy</tt> option for
-
# +accepts_nested_attributes_for+:
-
#
-
# class Person < ActiveRecord::Base
-
# has_one :address
-
# accepts_nested_attributes_for :address, allow_destroy: true
-
# end
-
#
-
# Now, when you use a form element with the <tt>_destroy</tt> parameter,
-
# with a value that evaluates to +true+, you will destroy the associated
-
# model (e.g. 1, '1', true, or 'true'):
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :address do |address_fields| %>
-
# ...
-
# Delete: <%= address_fields.check_box :_destroy %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# ==== One-to-many
-
#
-
# Consider a Person class which returns an _array_ of Project instances
-
# from the <tt>projects</tt> reader method and responds to the
-
# <tt>projects_attributes=</tt> writer method:
-
#
-
# class Person
-
# def projects
-
# [@project1, @project2]
-
# end
-
#
-
# def projects_attributes=(attributes)
-
# # Process the attributes hash
-
# end
-
# end
-
#
-
# Note that the <tt>projects_attributes=</tt> writer method is in fact
-
# required for fields_for to correctly identify <tt>:projects</tt> as a
-
# collection, and the correct indices to be set in the form markup.
-
#
-
# When projects is already an association on Person you can use
-
# +accepts_nested_attributes_for+ to define the writer method for you:
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :projects
-
# accepts_nested_attributes_for :projects
-
# end
-
#
-
# This model can now be used with a nested fields_for. The block given to
-
# the nested fields_for call will be repeated for each instance in the
-
# collection:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects do |project_fields| %>
-
# <% if project_fields.object.active? %>
-
# Name: <%= project_fields.text_field :name %>
-
# <% end %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# It's also possible to specify the instance to be used:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <% @person.projects.each do |project| %>
-
# <% if project.active? %>
-
# <%= person_form.fields_for :projects, project do |project_fields| %>
-
# Name: <%= project_fields.text_field :name %>
-
# <% end %>
-
# <% end %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# Or a collection to be used:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
-
# Name: <%= project_fields.text_field :name %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# If you want to destroy any of the associated models through the
-
# form, you have to enable it first using the <tt>:allow_destroy</tt>
-
# option for +accepts_nested_attributes_for+:
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :projects
-
# accepts_nested_attributes_for :projects, allow_destroy: true
-
# end
-
#
-
# This will allow you to specify which models to destroy in the
-
# attributes hash by adding a form element for the <tt>_destroy</tt>
-
# parameter with a value that evaluates to +true+
-
# (e.g. 1, '1', true, or 'true'):
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects do |project_fields| %>
-
# Delete: <%= project_fields.check_box :_destroy %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# When a collection is used you might want to know the index of each
-
# object into the array. For this purpose, the <tt>index</tt> method
-
# is available in the FormBuilder object.
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects do |project_fields| %>
-
# Project #<%= project_fields.index %>
-
# ...
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# Note that fields_for will automatically generate a hidden field
-
# to store the ID of the record. There are circumstances where this
-
# hidden field is not needed and you can pass <tt>include_id: false</tt>
-
# to prevent fields_for from rendering it automatically.
-
9
def fields_for(record_name, record_object = nil, options = {}, &block)
-
builder = instantiate_builder(record_name, record_object, options)
-
capture(builder, &block)
-
end
-
-
# Scopes input fields with either an explicit scope or model.
-
# Like +form_with+ does with <tt>:scope</tt> or <tt>:model</tt>,
-
# except it doesn't output the form tags.
-
#
-
# # Using a scope prefixes the input field names:
-
# <%= fields :comment do |fields| %>
-
# <%= fields.text_field :body %>
-
# <% end %>
-
# # => <input type="text" name="comment[body]">
-
#
-
# # Using a model infers the scope and assigns field values:
-
# <%= fields model: Comment.new(body: "full bodied") do |fields| %>
-
# <%= fields.text_field :body %>
-
# <% end %>
-
# # => <input type="text" name="comment[body]" value="full bodied">
-
#
-
# # Using +fields+ with +form_with+:
-
# <%= form_with model: @post do |form| %>
-
# <%= form.text_field :title %>
-
#
-
# <%= form.fields :comment do |fields| %>
-
# <%= fields.text_field :body %>
-
# <% end %>
-
# <% end %>
-
#
-
# Much like +form_with+ a FormBuilder instance associated with the scope
-
# or model is yielded, so any generated field names are prefixed with
-
# either the passed scope or the scope inferred from the <tt>:model</tt>.
-
#
-
# === Mixing with other form helpers
-
#
-
# While +form_with+ uses a FormBuilder object it's possible to mix and
-
# match the stand-alone FormHelper methods and methods
-
# from FormTagHelper:
-
#
-
# <%= fields model: @comment do |fields| %>
-
# <%= fields.text_field :body %>
-
#
-
# <%= text_area :commenter, :biography %>
-
# <%= check_box_tag "comment[all_caps]", "1", @comment.commenter.hulk_mode? %>
-
# <% end %>
-
#
-
# Same goes for the methods in FormOptionsHelper and DateHelper designed
-
# to work with an object as a base, like
-
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
-
9
def fields(scope = nil, model: nil, **options, &block)
-
options[:allow_method_names_outside_object] = true
-
options[:skip_default_ids] = !form_with_generates_ids
-
-
if model
-
scope ||= model_name_from_record_or_class(model).param_key
-
end
-
-
builder = instantiate_builder(scope, model, options)
-
capture(builder, &block)
-
end
-
-
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
-
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
-
# Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
-
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
-
# target labels for radio_button tags (where the value is used in the ID of the input tag).
-
#
-
# ==== Examples
-
# label(:post, :title)
-
# # => <label for="post_title">Title</label>
-
#
-
# You can localize your labels based on model and attribute names.
-
# For example you can define the following in your locale (e.g. en.yml)
-
#
-
# helpers:
-
# label:
-
# post:
-
# body: "Write your entire text here"
-
#
-
# Which then will result in
-
#
-
# label(:post, :body)
-
# # => <label for="post_body">Write your entire text here</label>
-
#
-
# Localization can also be based purely on the translation of the attribute-name
-
# (if you are using ActiveRecord):
-
#
-
# activerecord:
-
# attributes:
-
# post:
-
# cost: "Total cost"
-
#
-
# label(:post, :cost)
-
# # => <label for="post_cost">Total cost</label>
-
#
-
# label(:post, :title, "A short title")
-
# # => <label for="post_title">A short title</label>
-
#
-
# label(:post, :title, "A short title", class: "title_label")
-
# # => <label for="post_title" class="title_label">A short title</label>
-
#
-
# label(:post, :privacy, "Public Post", value: "public")
-
# # => <label for="post_privacy_public">Public Post</label>
-
#
-
# label(:post, :terms) do
-
# raw('Accept <a href="/terms">Terms</a>.')
-
# end
-
# # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
-
9
def label(object_name, method, content_or_options = nil, options = nil, &block)
-
Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
-
end
-
-
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
-
# shown.
-
#
-
# ==== Examples
-
# text_field(:post, :title, size: 20)
-
# # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
-
#
-
# text_field(:post, :title, class: "create_input")
-
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
-
#
-
# text_field(:post, :title, maxlength: 30, class: "title_input")
-
# # => <input type="text" id="post_title" name="post[title]" maxlength="30" size="30" value="#{@post.title}" class="title_input" />
-
#
-
# text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
-
# # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/>
-
#
-
# text_field(:snippet, :code, size: 20, class: 'code_input')
-
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
-
9
def text_field(object_name, method, options = {})
-
Tags::TextField.new(object_name, method, self, options).render
-
end
-
-
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
-
# shown. For security reasons this field is blank by default; pass in a value via +options+ if this is not desired.
-
#
-
# ==== Examples
-
# password_field(:login, :pass, size: 20)
-
# # => <input type="password" id="login_pass" name="login[pass]" size="20" />
-
#
-
# password_field(:account, :secret, class: "form_input", value: @account.secret)
-
# # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
-
#
-
# password_field(:user, :password, onchange: "if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }")
-
# # => <input type="password" id="user_password" name="user[password]" onchange="if ($('#user_password').val().length > 30) { alert('Your password needs to be shorter!'); }"/>
-
#
-
# password_field(:account, :pin, size: 20, class: 'form_input')
-
# # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
-
9
def password_field(object_name, method, options = {})
-
Tags::PasswordField.new(object_name, method, self, options).render
-
end
-
-
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
-
# shown.
-
#
-
# ==== Examples
-
# hidden_field(:signup, :pass_confirm)
-
# # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
-
#
-
# hidden_field(:post, :tag_list)
-
# # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
-
#
-
# hidden_field(:user, :token)
-
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
-
9
def hidden_field(object_name, method, options = {})
-
Tags::HiddenField.new(object_name, method, self, options).render
-
end
-
-
# Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
-
# shown.
-
#
-
# Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
-
#
-
# ==== Options
-
# * Creates standard HTML attributes for the tag.
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
-
# * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
-
#
-
# ==== Examples
-
# file_field(:user, :avatar)
-
# # => <input type="file" id="user_avatar" name="user[avatar]" />
-
#
-
# file_field(:post, :image, multiple: true)
-
# # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
-
#
-
# file_field(:post, :attached, accept: 'text/html')
-
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
-
#
-
# file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg')
-
# # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
-
#
-
# file_field(:attachment, :file, class: 'file_input')
-
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
-
9
def file_field(object_name, method, options = {})
-
Tags::FileField.new(object_name, method, self, convert_direct_upload_option_to_url(options.dup)).render
-
end
-
-
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
-
# on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+.
-
#
-
# ==== Examples
-
# text_area(:post, :body, cols: 20, rows: 40)
-
# # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
-
# # #{@post.body}
-
# # </textarea>
-
#
-
# text_area(:comment, :text, size: "20x30")
-
# # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
-
# # #{@comment.text}
-
# # </textarea>
-
#
-
# text_area(:application, :notes, cols: 40, rows: 15, class: 'app_input')
-
# # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
-
# # #{@application.notes}
-
# # </textarea>
-
#
-
# text_area(:entry, :body, size: "20x20", disabled: 'disabled')
-
# # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
-
# # #{@entry.body}
-
# # </textarea>
-
9
def text_area(object_name, method, options = {})
-
Tags::TextArea.new(object_name, method, self, options).render
-
end
-
-
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
-
# It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
-
# Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
-
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
-
#
-
# ==== Gotcha
-
#
-
# The HTML specification says unchecked check boxes are not successful, and
-
# thus web browsers do not send them. Unfortunately this introduces a gotcha:
-
# if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
-
# invoice the user unchecks its check box, no +paid+ parameter is sent. So,
-
# any mass-assignment idiom like
-
#
-
# @invoice.update(params[:invoice])
-
#
-
# wouldn't update the flag.
-
#
-
# To prevent this the helper generates an auxiliary hidden field before
-
# the very check box. The hidden field has the same name and its
-
# attributes mimic an unchecked check box.
-
#
-
# This way, the client either sends only the hidden field (representing
-
# the check box is unchecked), or both fields. Since the HTML specification
-
# says key/value pairs have to be sent in the same order they appear in the
-
# form, and parameters extraction gets the last occurrence of any repeated
-
# key in the query string, that works for ordinary forms.
-
#
-
# Unfortunately that workaround does not work when the check box goes
-
# within an array-like parameter, as in
-
#
-
# <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %>
-
# <%= form.check_box :paid %>
-
# ...
-
# <% end %>
-
#
-
# because parameter name repetition is precisely what Rails seeks to distinguish
-
# the elements of the array. For each item with a checked check box you
-
# get an extra ghost item with only that attribute, assigned to "0".
-
#
-
# In that case it is preferable to either use +check_box_tag+ or to use
-
# hashes instead of arrays.
-
#
-
# # Let's say that @post.validated? is 1:
-
# check_box("post", "validated")
-
# # => <input name="post[validated]" type="hidden" value="0" />
-
# # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
-
#
-
# # Let's say that @puppy.gooddog is "no":
-
# check_box("puppy", "gooddog", {}, "yes", "no")
-
# # => <input name="puppy[gooddog]" type="hidden" value="no" />
-
# # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
-
#
-
# check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no")
-
# # => <input name="eula[accepted]" type="hidden" value="no" />
-
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
-
9
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
-
Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
-
end
-
-
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
-
# radio button will be checked.
-
#
-
# To force the radio button to be checked pass <tt>checked: true</tt> in the
-
# +options+ hash. You may pass HTML options there as well.
-
#
-
# # Let's say that @post.category returns "rails":
-
# radio_button("post", "category", "rails")
-
# radio_button("post", "category", "java")
-
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
-
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
-
#
-
# # Let's say that @user.receive_newsletter returns "no":
-
# radio_button("user", "receive_newsletter", "yes")
-
# radio_button("user", "receive_newsletter", "no")
-
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
-
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
-
9
def radio_button(object_name, method, tag_value, options = {})
-
Tags::RadioButton.new(object_name, method, self, tag_value, options).render
-
end
-
-
# Returns a text_field of type "color".
-
#
-
# color_field("car", "color")
-
# # => <input id="car_color" name="car[color]" type="color" value="#000000" />
-
9
def color_field(object_name, method, options = {})
-
Tags::ColorField.new(object_name, method, self, options).render
-
end
-
-
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
-
# some browsers.
-
#
-
# search_field(:user, :name)
-
# # => <input id="user_name" name="user[name]" type="search" />
-
# search_field(:user, :name, autosave: false)
-
# # => <input autosave="false" id="user_name" name="user[name]" type="search" />
-
# search_field(:user, :name, results: 3)
-
# # => <input id="user_name" name="user[name]" results="3" type="search" />
-
# # Assume request.host returns "www.example.com"
-
# search_field(:user, :name, autosave: true)
-
# # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" />
-
# search_field(:user, :name, onsearch: true)
-
# # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
-
# search_field(:user, :name, autosave: false, onsearch: true)
-
# # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
-
# search_field(:user, :name, autosave: true, onsearch: true)
-
# # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" />
-
9
def search_field(object_name, method, options = {})
-
Tags::SearchField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "tel".
-
#
-
# telephone_field("user", "phone")
-
# # => <input id="user_phone" name="user[phone]" type="tel" />
-
#
-
9
def telephone_field(object_name, method, options = {})
-
Tags::TelField.new(object_name, method, self, options).render
-
end
-
# aliases telephone_field
-
9
alias phone_field telephone_field
-
-
# Returns a text_field of type "date".
-
#
-
# date_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="date" />
-
#
-
# The default value is generated by trying to call +strftime+ with "%Y-%m-%d"
-
# on the object's value, which makes it behave as expected for instances
-
# of DateTime and ActiveSupport::TimeWithZone. You can still override that
-
# by passing the "value" option explicitly, e.g.
-
#
-
# @user.born_on = Date.new(1984, 1, 27)
-
# date_field("user", "born_on", value: "1984-05-12")
-
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
-
#
-
# You can create values for the "min" and "max" attributes by passing
-
# instances of Date or Time to the options hash.
-
#
-
# date_field("user", "born_on", min: Date.today)
-
# # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
-
#
-
# Alternatively, you can pass a String formatted as an ISO8601 date as the
-
# values for "min" and "max."
-
#
-
# date_field("user", "born_on", min: "2014-05-20")
-
# # => <input id="user_born_on" name="user[born_on]" type="date" min="2014-05-20" />
-
#
-
9
def date_field(object_name, method, options = {})
-
Tags::DateField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "time".
-
#
-
# The default value is generated by trying to call +strftime+ with "%T.%L"
-
# on the object's value. It is still possible to override that
-
# by passing the "value" option.
-
#
-
# === Options
-
# * Accepts same options as time_field_tag
-
#
-
# === Example
-
# time_field("task", "started_at")
-
# # => <input id="task_started_at" name="task[started_at]" type="time" />
-
#
-
# You can create values for the "min" and "max" attributes by passing
-
# instances of Date or Time to the options hash.
-
#
-
# time_field("task", "started_at", min: Time.now)
-
# # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
-
#
-
# Alternatively, you can pass a String formatted as an ISO8601 time as the
-
# values for "min" and "max."
-
#
-
# time_field("task", "started_at", min: "01:00:00")
-
# # => <input id="task_started_at" name="task[started_at]" type="time" min="01:00:00.000" />
-
#
-
9
def time_field(object_name, method, options = {})
-
Tags::TimeField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "datetime-local".
-
#
-
# datetime_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
-
#
-
# The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
-
# on the object's value, which makes it behave as expected for instances
-
# of DateTime and ActiveSupport::TimeWithZone.
-
#
-
# @user.born_on = Date.new(1984, 1, 12)
-
# datetime_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
-
#
-
# You can create values for the "min" and "max" attributes by passing
-
# instances of Date or Time to the options hash.
-
#
-
# datetime_field("user", "born_on", min: Date.today)
-
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
-
#
-
# Alternatively, you can pass a String formatted as an ISO8601 datetime as
-
# the values for "min" and "max."
-
#
-
# datetime_field("user", "born_on", min: "2014-05-20T00:00:00")
-
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
-
#
-
9
def datetime_field(object_name, method, options = {})
-
Tags::DatetimeLocalField.new(object_name, method, self, options).render
-
end
-
-
9
alias datetime_local_field datetime_field
-
-
# Returns a text_field of type "month".
-
#
-
# month_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="month" />
-
#
-
# The default value is generated by trying to call +strftime+ with "%Y-%m"
-
# on the object's value, which makes it behave as expected for instances
-
# of DateTime and ActiveSupport::TimeWithZone.
-
#
-
# @user.born_on = Date.new(1984, 1, 27)
-
# month_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" />
-
#
-
9
def month_field(object_name, method, options = {})
-
Tags::MonthField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "week".
-
#
-
# week_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="week" />
-
#
-
# The default value is generated by trying to call +strftime+ with "%Y-W%W"
-
# on the object's value, which makes it behave as expected for instances
-
# of DateTime and ActiveSupport::TimeWithZone.
-
#
-
# @user.born_on = Date.new(1984, 5, 12)
-
# week_field("user", "born_on")
-
# # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" />
-
#
-
9
def week_field(object_name, method, options = {})
-
Tags::WeekField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "url".
-
#
-
# url_field("user", "homepage")
-
# # => <input id="user_homepage" name="user[homepage]" type="url" />
-
#
-
9
def url_field(object_name, method, options = {})
-
Tags::UrlField.new(object_name, method, self, options).render
-
end
-
-
# Returns a text_field of type "email".
-
#
-
# email_field("user", "address")
-
# # => <input id="user_address" name="user[address]" type="email" />
-
#
-
9
def email_field(object_name, method, options = {})
-
Tags::EmailField.new(object_name, method, self, options).render
-
end
-
-
# Returns an input tag of type "number".
-
#
-
# ==== Options
-
# * Accepts same options as number_field_tag
-
9
def number_field(object_name, method, options = {})
-
Tags::NumberField.new(object_name, method, self, options).render
-
end
-
-
# Returns an input tag of type "range".
-
#
-
# ==== Options
-
# * Accepts same options as range_field_tag
-
9
def range_field(object_name, method, options = {})
-
Tags::RangeField.new(object_name, method, self, options).render
-
end
-
-
9
private
-
9
def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
-
skip_enforcing_utf8: nil, **options)
-
html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
-
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
-
html_options[:enforce_utf8] = !skip_enforcing_utf8 unless skip_enforcing_utf8.nil?
-
-
html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
-
-
# The following URL is unescaped, this is just a hash of options, and it is the
-
# responsibility of the caller to escape all the values.
-
html_options[:action] = url_for(url_for_options || {})
-
html_options[:"accept-charset"] = "UTF-8"
-
html_options[:"data-remote"] = true unless local
-
-
html_options[:authenticity_token] = options.delete(:authenticity_token)
-
-
if !local && html_options[:authenticity_token].blank?
-
html_options[:authenticity_token] = embed_authenticity_token_in_remote_forms
-
end
-
-
if html_options[:authenticity_token] == true
-
# Include the default authenticity_token, which is only generated when it's set to nil,
-
# but we needed the true value to override the default of no authenticity_token on data-remote.
-
html_options[:authenticity_token] = nil
-
end
-
-
html_options.stringify_keys!
-
end
-
-
9
def instantiate_builder(record_name, record_object, options)
-
case record_name
-
when String, Symbol
-
object = record_object
-
object_name = record_name
-
else
-
object = record_name
-
object_name = model_name_from_record_or_class(object).param_key if object
-
end
-
-
builder = options[:builder] || default_form_builder_class
-
builder.new(object_name, object, self, options)
-
end
-
-
9
def default_form_builder_class
-
builder = default_form_builder || ActionView::Base.default_form_builder
-
builder.respond_to?(:constantize) ? builder.constantize : builder
-
end
-
end
-
-
# A +FormBuilder+ object is associated with a particular model object and
-
# allows you to generate fields associated with the model object. The
-
# +FormBuilder+ object is yielded when using +form_for+ or +fields_for+.
-
# For example:
-
#
-
# <%= form_for @person do |person_form| %>
-
# Name: <%= person_form.text_field :name %>
-
# Admin: <%= person_form.check_box :admin %>
-
# <% end %>
-
#
-
# In the above block, a +FormBuilder+ object is yielded as the
-
# +person_form+ variable. This allows you to generate the +text_field+
-
# and +check_box+ fields by specifying their eponymous methods, which
-
# modify the underlying template and associates the <tt>@person</tt> model object
-
# with the form.
-
#
-
# The +FormBuilder+ object can be thought of as serving as a proxy for the
-
# methods in the +FormHelper+ module. This class, however, allows you to
-
# call methods with the model object you are building the form for.
-
#
-
# You can create your own custom FormBuilder templates by subclassing this
-
# class. For example:
-
#
-
# class MyFormBuilder < ActionView::Helpers::FormBuilder
-
# def div_radio_button(method, tag_value, options = {})
-
# @template.content_tag(:div,
-
# @template.radio_button(
-
# @object_name, method, tag_value, objectify_options(options)
-
# )
-
# )
-
# end
-
# end
-
#
-
# The above code creates a new method +div_radio_button+ which wraps a div
-
# around the new radio button. Note that when options are passed in, you
-
# must call +objectify_options+ in order for the model object to get
-
# correctly passed to the method. If +objectify_options+ is not called,
-
# then the newly created helper will not be linked back to the model.
-
#
-
# The +div_radio_button+ code from above can now be used as follows:
-
#
-
# <%= form_for @person, :builder => MyFormBuilder do |f| %>
-
# I am a child: <%= f.div_radio_button(:admin, "child") %>
-
# I am an adult: <%= f.div_radio_button(:admin, "adult") %>
-
# <% end -%>
-
#
-
# The standard set of helper methods for form building are located in the
-
# +field_helpers+ class attribute.
-
9
class FormBuilder
-
9
include ModelNaming
-
-
# The methods which wrap a form helper call.
-
9
class_attribute :field_helpers, default: [
-
:fields_for, :fields, :label, :text_field, :password_field,
-
:hidden_field, :file_field, :text_area, :check_box,
-
:radio_button, :color_field, :search_field,
-
:telephone_field, :phone_field, :date_field,
-
:time_field, :datetime_field, :datetime_local_field,
-
:month_field, :week_field, :url_field, :email_field,
-
:number_field, :range_field
-
]
-
-
9
attr_accessor :object_name, :object, :options
-
-
9
attr_reader :multipart, :index
-
9
alias :multipart? :multipart
-
-
9
def multipart=(multipart)
-
@multipart = multipart
-
-
if parent_builder = @options[:parent_builder]
-
parent_builder.multipart = multipart
-
end
-
end
-
-
9
def self._to_partial_path
-
@_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
-
end
-
-
9
def to_partial_path
-
self.class._to_partial_path
-
end
-
-
9
def to_model
-
self
-
end
-
-
9
def initialize(object_name, object, template, options)
-
@nested_child_index = {}
-
@object_name, @object, @template, @options = object_name, object, template, options
-
@default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {}
-
@default_html_options = @default_options.except(:skip_default_ids, :allow_method_names_outside_object)
-
-
convert_to_legacy_options(@options)
-
-
if @object_name&.end_with?("[]")
-
if (object ||= @template.instance_variable_get("@#{@object_name[0..-3]}")) && object.respond_to?(:to_param)
-
@auto_index = object.to_param
-
else
-
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
-
end
-
end
-
-
@multipart = nil
-
@index = options[:index] || options[:child_index]
-
end
-
-
##
-
# :method: text_field
-
#
-
# :call-seq: text_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#text_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.text_field :name %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: password_field
-
#
-
# :call-seq: password_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#password_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.password_field :password %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: text_area
-
#
-
# :call-seq: text_area(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#text_area for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.text_area :detail %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: color_field
-
#
-
# :call-seq: color_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#color_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.color_field :favorite_color %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: search_field
-
#
-
# :call-seq: search_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#search_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.search_field :name %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: telephone_field
-
#
-
# :call-seq: telephone_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#telephone_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.telephone_field :phone %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: phone_field
-
#
-
# :call-seq: phone_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#phone_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.phone_field :phone %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: date_field
-
#
-
# :call-seq: date_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#date_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.date_field :born_on %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: time_field
-
#
-
# :call-seq: time_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#time_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.time_field :born_at %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: datetime_field
-
#
-
# :call-seq: datetime_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#datetime_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.datetime_field :graduation_day %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: datetime_local_field
-
#
-
# :call-seq: datetime_local_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#datetime_local_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.datetime_local_field :graduation_day %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: month_field
-
#
-
# :call-seq: month_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#month_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.month_field :birthday_month %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: week_field
-
#
-
# :call-seq: week_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#week_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.week_field :birthday_week %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: url_field
-
#
-
# :call-seq: url_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#url_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.url_field :homepage %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: email_field
-
#
-
# :call-seq: email_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#email_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.email_field :address %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: number_field
-
#
-
# :call-seq: number_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#number_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.number_field :age %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
##
-
# :method: range_field
-
#
-
# :call-seq: range_field(method, options = {})
-
#
-
# Wraps ActionView::Helpers::FormHelper#range_field for form builders:
-
#
-
# <%= form_with model: @user do |f| %>
-
# <%= f.range_field :age %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
-
9
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
-
153
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
-
def #{selector}(method, options = {}) # def text_field(method, options = {})
-
@template.send( # @template.send(
-
#{selector.inspect}, # :text_field,
-
@object_name, # @object_name,
-
method, # method,
-
objectify_options(options)) # objectify_options(options))
-
end # end
-
RUBY_EVAL
-
end
-
-
# Creates a scope around a specific model object like form_for, but
-
# doesn't create the form tags themselves. This makes fields_for suitable
-
# for specifying additional model objects in the same form.
-
#
-
# Although the usage and purpose of +fields_for+ is similar to +form_for+'s,
-
# its method signature is slightly different. Like +form_for+, it yields
-
# a FormBuilder object associated with a particular model object to a block,
-
# and within the block allows methods to be called on the builder to
-
# generate fields associated with the model object. Fields may reflect
-
# a model object in two ways - how they are named (hence how submitted
-
# values appear within the +params+ hash in the controller) and what
-
# default values are shown when the form the fields appear in is first
-
# displayed. In order for both of these features to be specified independently,
-
# both an object name (represented by either a symbol or string) and the
-
# object itself can be passed to the method separately -
-
#
-
# <%= form_for @person do |person_form| %>
-
# First name: <%= person_form.text_field :first_name %>
-
# Last name : <%= person_form.text_field :last_name %>
-
#
-
# <%= fields_for :permission, @person.permission do |permission_fields| %>
-
# Admin? : <%= permission_fields.check_box :admin %>
-
# <% end %>
-
#
-
# <%= person_form.submit %>
-
# <% end %>
-
#
-
# In this case, the checkbox field will be represented by an HTML +input+
-
# tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted
-
# value will appear in the controller as <tt>params[:permission][:admin]</tt>.
-
# If <tt>@person.permission</tt> is an existing record with an attribute
-
# +admin+, the initial state of the checkbox when first displayed will
-
# reflect the value of <tt>@person.permission.admin</tt>.
-
#
-
# Often this can be simplified by passing just the name of the model
-
# object to +fields_for+ -
-
#
-
# <%= fields_for :permission do |permission_fields| %>
-
# Admin?: <%= permission_fields.check_box :admin %>
-
# <% end %>
-
#
-
# ...in which case, if <tt>:permission</tt> also happens to be the name of an
-
# instance variable <tt>@permission</tt>, the initial state of the input
-
# field will reflect the value of that variable's attribute <tt>@permission.admin</tt>.
-
#
-
# Alternatively, you can pass just the model object itself (if the first
-
# argument isn't a string or symbol +fields_for+ will realize that the
-
# name has been omitted) -
-
#
-
# <%= fields_for @person.permission do |permission_fields| %>
-
# Admin?: <%= permission_fields.check_box :admin %>
-
# <% end %>
-
#
-
# and +fields_for+ will derive the required name of the field from the
-
# _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
-
# of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
-
#
-
# Note: This also works for the methods in FormOptionsHelper and
-
# DateHelper that are designed to work with an object as base, like
-
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
-
#
-
# === Nested Attributes Examples
-
#
-
# When the object belonging to the current scope has a nested attribute
-
# writer for a certain attribute, fields_for will yield a new scope
-
# for that attribute. This allows you to create forms that set or change
-
# the attributes of a parent object and its associations in one go.
-
#
-
# Nested attribute writers are normal setter methods named after an
-
# association. The most common way of defining these writers is either
-
# with +accepts_nested_attributes_for+ in a model definition or by
-
# defining a method with the proper name. For example: the attribute
-
# writer for the association <tt>:address</tt> is called
-
# <tt>address_attributes=</tt>.
-
#
-
# Whether a one-to-one or one-to-many style form builder will be yielded
-
# depends on whether the normal reader method returns a _single_ object
-
# or an _array_ of objects.
-
#
-
# ==== One-to-one
-
#
-
# Consider a Person class which returns a _single_ Address from the
-
# <tt>address</tt> reader method and responds to the
-
# <tt>address_attributes=</tt> writer method:
-
#
-
# class Person
-
# def address
-
# @address
-
# end
-
#
-
# def address_attributes=(attributes)
-
# # Process the attributes hash
-
# end
-
# end
-
#
-
# This model can now be used with a nested fields_for, like so:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :address do |address_fields| %>
-
# Street : <%= address_fields.text_field :street %>
-
# Zip code: <%= address_fields.text_field :zip_code %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# When address is already an association on a Person you can use
-
# +accepts_nested_attributes_for+ to define the writer method for you:
-
#
-
# class Person < ActiveRecord::Base
-
# has_one :address
-
# accepts_nested_attributes_for :address
-
# end
-
#
-
# If you want to destroy the associated model through the form, you have
-
# to enable it first using the <tt>:allow_destroy</tt> option for
-
# +accepts_nested_attributes_for+:
-
#
-
# class Person < ActiveRecord::Base
-
# has_one :address
-
# accepts_nested_attributes_for :address, allow_destroy: true
-
# end
-
#
-
# Now, when you use a form element with the <tt>_destroy</tt> parameter,
-
# with a value that evaluates to +true+, you will destroy the associated
-
# model (e.g. 1, '1', true, or 'true'):
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :address do |address_fields| %>
-
# ...
-
# Delete: <%= address_fields.check_box :_destroy %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# ==== One-to-many
-
#
-
# Consider a Person class which returns an _array_ of Project instances
-
# from the <tt>projects</tt> reader method and responds to the
-
# <tt>projects_attributes=</tt> writer method:
-
#
-
# class Person
-
# def projects
-
# [@project1, @project2]
-
# end
-
#
-
# def projects_attributes=(attributes)
-
# # Process the attributes hash
-
# end
-
# end
-
#
-
# Note that the <tt>projects_attributes=</tt> writer method is in fact
-
# required for fields_for to correctly identify <tt>:projects</tt> as a
-
# collection, and the correct indices to be set in the form markup.
-
#
-
# When projects is already an association on Person you can use
-
# +accepts_nested_attributes_for+ to define the writer method for you:
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :projects
-
# accepts_nested_attributes_for :projects
-
# end
-
#
-
# This model can now be used with a nested fields_for. The block given to
-
# the nested fields_for call will be repeated for each instance in the
-
# collection:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects do |project_fields| %>
-
# <% if project_fields.object.active? %>
-
# Name: <%= project_fields.text_field :name %>
-
# <% end %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# It's also possible to specify the instance to be used:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <% @person.projects.each do |project| %>
-
# <% if project.active? %>
-
# <%= person_form.fields_for :projects, project do |project_fields| %>
-
# Name: <%= project_fields.text_field :name %>
-
# <% end %>
-
# <% end %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# Or a collection to be used:
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
-
# Name: <%= project_fields.text_field :name %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# If you want to destroy any of the associated models through the
-
# form, you have to enable it first using the <tt>:allow_destroy</tt>
-
# option for +accepts_nested_attributes_for+:
-
#
-
# class Person < ActiveRecord::Base
-
# has_many :projects
-
# accepts_nested_attributes_for :projects, allow_destroy: true
-
# end
-
#
-
# This will allow you to specify which models to destroy in the
-
# attributes hash by adding a form element for the <tt>_destroy</tt>
-
# parameter with a value that evaluates to +true+
-
# (e.g. 1, '1', true, or 'true'):
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects do |project_fields| %>
-
# Delete: <%= project_fields.check_box :_destroy %>
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# When a collection is used you might want to know the index of each
-
# object into the array. For this purpose, the <tt>index</tt> method
-
# is available in the FormBuilder object.
-
#
-
# <%= form_for @person do |person_form| %>
-
# ...
-
# <%= person_form.fields_for :projects do |project_fields| %>
-
# Project #<%= project_fields.index %>
-
# ...
-
# <% end %>
-
# ...
-
# <% end %>
-
#
-
# Note that fields_for will automatically generate a hidden field
-
# to store the ID of the record. There are circumstances where this
-
# hidden field is not needed and you can pass <tt>include_id: false</tt>
-
# to prevent fields_for from rendering it automatically.
-
9
def fields_for(record_name, record_object = nil, fields_options = {}, &block)
-
fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
-
fields_options[:builder] ||= options[:builder]
-
fields_options[:namespace] = options[:namespace]
-
fields_options[:parent_builder] = self
-
-
case record_name
-
when String, Symbol
-
if nested_attributes_association?(record_name)
-
return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
-
end
-
else
-
record_object = record_name.is_a?(Array) ? record_name.last : record_name
-
record_name = model_name_from_record_or_class(record_object).param_key
-
end
-
-
object_name = @object_name
-
index = if options.has_key?(:index)
-
options[:index]
-
elsif defined?(@auto_index)
-
object_name = object_name.to_s.delete_suffix("[]")
-
@auto_index
-
end
-
-
record_name = if index
-
"#{object_name}[#{index}][#{record_name}]"
-
elsif record_name.end_with?("[]")
-
"#{object_name}[#{record_name[0..-3]}][#{record_object.id}]"
-
else
-
"#{object_name}[#{record_name}]"
-
end
-
fields_options[:child_index] = index
-
-
@template.fields_for(record_name, record_object, fields_options, &block)
-
end
-
-
# See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
-
9
def fields(scope = nil, model: nil, **options, &block)
-
options[:allow_method_names_outside_object] = true
-
options[:skip_default_ids] = !FormHelper.form_with_generates_ids
-
-
convert_to_legacy_options(options)
-
-
fields_for(scope || model, model, options, &block)
-
end
-
-
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
-
# is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
-
# Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
-
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
-
# target labels for radio_button tags (where the value is used in the ID of the input tag).
-
#
-
# ==== Examples
-
# label(:title)
-
# # => <label for="post_title">Title</label>
-
#
-
# You can localize your labels based on model and attribute names.
-
# For example you can define the following in your locale (e.g. en.yml)
-
#
-
# helpers:
-
# label:
-
# post:
-
# body: "Write your entire text here"
-
#
-
# Which then will result in
-
#
-
# label(:body)
-
# # => <label for="post_body">Write your entire text here</label>
-
#
-
# Localization can also be based purely on the translation of the attribute-name
-
# (if you are using ActiveRecord):
-
#
-
# activerecord:
-
# attributes:
-
# post:
-
# cost: "Total cost"
-
#
-
# label(:cost)
-
# # => <label for="post_cost">Total cost</label>
-
#
-
# label(:title, "A short title")
-
# # => <label for="post_title">A short title</label>
-
#
-
# label(:title, "A short title", class: "title_label")
-
# # => <label for="post_title" class="title_label">A short title</label>
-
#
-
# label(:privacy, "Public Post", value: "public")
-
# # => <label for="post_privacy_public">Public Post</label>
-
#
-
# label(:terms) do
-
# raw('Accept <a href="/terms">Terms</a>.')
-
# end
-
# # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
-
9
def label(method, text = nil, options = {}, &block)
-
@template.label(@object_name, method, text, objectify_options(options), &block)
-
end
-
-
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
-
# It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
-
# Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
-
# while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
-
#
-
# ==== Gotcha
-
#
-
# The HTML specification says unchecked check boxes are not successful, and
-
# thus web browsers do not send them. Unfortunately this introduces a gotcha:
-
# if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
-
# invoice the user unchecks its check box, no +paid+ parameter is sent. So,
-
# any mass-assignment idiom like
-
#
-
# @invoice.update(params[:invoice])
-
#
-
# wouldn't update the flag.
-
#
-
# To prevent this the helper generates an auxiliary hidden field before
-
# the very check box. The hidden field has the same name and its
-
# attributes mimic an unchecked check box.
-
#
-
# This way, the client either sends only the hidden field (representing
-
# the check box is unchecked), or both fields. Since the HTML specification
-
# says key/value pairs have to be sent in the same order they appear in the
-
# form, and parameters extraction gets the last occurrence of any repeated
-
# key in the query string, that works for ordinary forms.
-
#
-
# Unfortunately that workaround does not work when the check box goes
-
# within an array-like parameter, as in
-
#
-
# <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %>
-
# <%= form.check_box :paid %>
-
# ...
-
# <% end %>
-
#
-
# because parameter name repetition is precisely what Rails seeks to distinguish
-
# the elements of the array. For each item with a checked check box you
-
# get an extra ghost item with only that attribute, assigned to "0".
-
#
-
# In that case it is preferable to either use +check_box_tag+ or to use
-
# hashes instead of arrays.
-
#
-
# # Let's say that @post.validated? is 1:
-
# check_box("validated")
-
# # => <input name="post[validated]" type="hidden" value="0" />
-
# # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
-
#
-
# # Let's say that @puppy.gooddog is "no":
-
# check_box("gooddog", {}, "yes", "no")
-
# # => <input name="puppy[gooddog]" type="hidden" value="no" />
-
# # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
-
#
-
# # Let's say that @eula.accepted is "no":
-
# check_box("accepted", { class: 'eula_check' }, "yes", "no")
-
# # => <input name="eula[accepted]" type="hidden" value="no" />
-
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
-
9
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
-
@template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
-
end
-
-
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
-
# radio button will be checked.
-
#
-
# To force the radio button to be checked pass <tt>checked: true</tt> in the
-
# +options+ hash. You may pass HTML options there as well.
-
#
-
# # Let's say that @post.category returns "rails":
-
# radio_button("category", "rails")
-
# radio_button("category", "java")
-
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
-
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
-
#
-
# # Let's say that @user.receive_newsletter returns "no":
-
# radio_button("receive_newsletter", "yes")
-
# radio_button("receive_newsletter", "no")
-
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
-
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
-
9
def radio_button(method, tag_value, options = {})
-
@template.radio_button(@object_name, method, tag_value, objectify_options(options))
-
end
-
-
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
-
# shown.
-
#
-
# ==== Examples
-
# # Let's say that @signup.pass_confirm returns true:
-
# hidden_field(:pass_confirm)
-
# # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="true" />
-
#
-
# # Let's say that @post.tag_list returns "blog, ruby":
-
# hidden_field(:tag_list)
-
# # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="blog, ruby" />
-
#
-
# # Let's say that @user.token returns "abcde":
-
# hidden_field(:token)
-
# # => <input type="hidden" id="user_token" name="user[token]" value="abcde" />
-
#
-
9
def hidden_field(method, options = {})
-
@emitted_hidden_id = true if method == :id
-
@template.hidden_field(@object_name, method, objectify_options(options))
-
end
-
-
# Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
-
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
-
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
-
# shown.
-
#
-
# Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
-
#
-
# ==== Options
-
# * Creates standard HTML attributes for the tag.
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
-
# * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
-
#
-
# ==== Examples
-
# # Let's say that @user has avatar:
-
# file_field(:avatar)
-
# # => <input type="file" id="user_avatar" name="user[avatar]" />
-
#
-
# # Let's say that @post has image:
-
# file_field(:image, :multiple => true)
-
# # => <input type="file" id="post_image" name="post[image][]" multiple="multiple" />
-
#
-
# # Let's say that @post has attached:
-
# file_field(:attached, accept: 'text/html')
-
# # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
-
#
-
# # Let's say that @post has image:
-
# file_field(:image, accept: 'image/png,image/gif,image/jpeg')
-
# # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" />
-
#
-
# # Let's say that @attachment has file:
-
# file_field(:file, class: 'file_input')
-
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
-
9
def file_field(method, options = {})
-
self.multipart = true
-
@template.file_field(@object_name, method, objectify_options(options))
-
end
-
-
# Add the submit button for the given form. When no value is given, it checks
-
# if the object is a new resource or not to create the proper label:
-
#
-
# <%= form_for @post do |f| %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
-
# submit button label; otherwise, it uses "Update Post".
-
#
-
# Those labels can be customized using I18n under the +helpers.submit+ key and using
-
# <tt>%{model}</tt> for translation interpolation:
-
#
-
# en:
-
# helpers:
-
# submit:
-
# create: "Create a %{model}"
-
# update: "Confirm changes to %{model}"
-
#
-
# It also searches for a key specific to the given object:
-
#
-
# en:
-
# helpers:
-
# submit:
-
# post:
-
# create: "Add %{model}"
-
#
-
9
def submit(value = nil, options = {})
-
value, options = nil, value if value.is_a?(Hash)
-
value ||= submit_default_value
-
@template.submit_tag(value, options)
-
end
-
-
# Add the submit button for the given form. When no value is given, it checks
-
# if the object is a new resource or not to create the proper label:
-
#
-
# <%= form_for @post do |f| %>
-
# <%= f.button %>
-
# <% end %>
-
#
-
# In the example above, if <tt>@post</tt> is a new record, it will use "Create Post" as
-
# button label; otherwise, it uses "Update Post".
-
#
-
# Those labels can be customized using I18n under the +helpers.submit+ key
-
# (the same as submit helper) and using <tt>%{model}</tt> for translation interpolation:
-
#
-
# en:
-
# helpers:
-
# submit:
-
# create: "Create a %{model}"
-
# update: "Confirm changes to %{model}"
-
#
-
# It also searches for a key specific to the given object:
-
#
-
# en:
-
# helpers:
-
# submit:
-
# post:
-
# create: "Add %{model}"
-
#
-
# ==== Examples
-
# button("Create post")
-
# # => <button name='button' type='submit'>Create post</button>
-
#
-
# button do
-
# content_tag(:strong, 'Ask me!')
-
# end
-
# # => <button name='button' type='submit'>
-
# # <strong>Ask me!</strong>
-
# # </button>
-
#
-
9
def button(value = nil, options = {}, &block)
-
value, options = nil, value if value.is_a?(Hash)
-
value ||= submit_default_value
-
@template.button_tag(value, options, &block)
-
end
-
-
9
def emitted_hidden_id? # :nodoc:
-
@emitted_hidden_id ||= nil
-
end
-
-
9
private
-
9
def objectify_options(options)
-
result = @default_options.merge(options)
-
result[:object] = @object
-
result
-
end
-
-
9
def submit_default_value
-
object = convert_to_model(@object)
-
key = object ? (object.persisted? ? :update : :create) : :submit
-
-
model = if object.respond_to?(:model_name)
-
object.model_name.human
-
else
-
@object_name.to_s.humanize
-
end
-
-
defaults = []
-
# Object is a model and it is not overwritten by as and scope option.
-
if object.respond_to?(:model_name) && object_name.to_s == model.downcase
-
defaults << :"helpers.submit.#{object.model_name.i18n_key}.#{key}"
-
else
-
defaults << :"helpers.submit.#{object_name}.#{key}"
-
end
-
defaults << :"helpers.submit.#{key}"
-
defaults << "#{key.to_s.humanize} #{model}"
-
-
I18n.t(defaults.shift, model: model, default: defaults)
-
end
-
-
9
def nested_attributes_association?(association_name)
-
@object.respond_to?("#{association_name}_attributes=")
-
end
-
-
9
def fields_for_with_nested_attributes(association_name, association, options, block)
-
name = "#{object_name}[#{association_name}_attributes]"
-
association = convert_to_model(association)
-
-
if association.respond_to?(:persisted?)
-
association = [association] if @object.send(association_name).respond_to?(:to_ary)
-
elsif !association.respond_to?(:to_ary)
-
association = @object.send(association_name)
-
end
-
-
if association.respond_to?(:to_ary)
-
explicit_child_index = options[:child_index]
-
output = ActiveSupport::SafeBuffer.new
-
association.each do |child|
-
if explicit_child_index
-
options[:child_index] = explicit_child_index.call if explicit_child_index.respond_to?(:call)
-
else
-
options[:child_index] = nested_child_index(name)
-
end
-
output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
-
end
-
output
-
elsif association
-
fields_for_nested_model(name, association, options, block)
-
end
-
end
-
-
9
def fields_for_nested_model(name, object, fields_options, block)
-
object = convert_to_model(object)
-
emit_hidden_id = object.persisted? && fields_options.fetch(:include_id) {
-
options.fetch(:include_id, true)
-
}
-
-
@template.fields_for(name, object, fields_options) do |f|
-
output = @template.capture(f, &block)
-
output.concat f.hidden_field(:id) if output && emit_hidden_id && !f.emitted_hidden_id?
-
output
-
end
-
end
-
-
9
def nested_child_index(name)
-
@nested_child_index[name] ||= -1
-
@nested_child_index[name] += 1
-
end
-
-
9
def convert_to_legacy_options(options)
-
if options.key?(:skip_id)
-
options[:include_id] = !options.delete(:skip_id)
-
end
-
end
-
end
-
end
-
-
9
ActiveSupport.on_load(:action_view) do
-
3
cattr_accessor :default_form_builder, instance_writer: false, instance_reader: false, default: ::ActionView::Helpers::FormBuilder
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "cgi"
-
9
require "erb"
-
9
require "action_view/helpers/form_helper"
-
9
require "active_support/core_ext/string/output_safety"
-
9
require "active_support/core_ext/array/extract_options"
-
9
require "active_support/core_ext/array/wrap"
-
-
9
module ActionView
-
# = Action View Form Option Helpers
-
9
module Helpers #:nodoc:
-
# Provides a number of methods for turning different kinds of containers into a set of option tags.
-
#
-
# The <tt>collection_select</tt>, <tt>select</tt> and <tt>time_zone_select</tt> methods take an <tt>options</tt> parameter, a hash:
-
#
-
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
-
#
-
# select("post", "category", Post::CATEGORIES, { include_blank: true })
-
#
-
# could become:
-
#
-
# <select name="post[category]" id="post_category">
-
# <option value="" label=" "></option>
-
# <option value="joke">joke</option>
-
# <option value="poem">poem</option>
-
# </select>
-
#
-
# Another common case is a select tag for a <tt>belongs_to</tt>-associated object.
-
#
-
# Example with <tt>@post.person_id => 2</tt>:
-
#
-
# select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: 'None' })
-
#
-
# could become:
-
#
-
# <select name="post[person_id]" id="post_person_id">
-
# <option value="">None</option>
-
# <option value="1">David</option>
-
# <option value="2" selected="selected">Eileen</option>
-
# <option value="3">Rafael</option>
-
# </select>
-
#
-
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
-
#
-
# select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { prompt: 'Select Person' })
-
#
-
# could become:
-
#
-
# <select name="post[person_id]" id="post_person_id">
-
# <option value="">Select Person</option>
-
# <option value="1">David</option>
-
# <option value="2">Eileen</option>
-
# <option value="3">Rafael</option>
-
# </select>
-
#
-
# * <tt>:index</tt> - like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this
-
# option to be in the +html_options+ parameter.
-
#
-
# select("album[]", "genre", %w[rap rock country], {}, { index: nil })
-
#
-
# becomes:
-
#
-
# <select name="album[][genre]" id="album__genre">
-
# <option value="rap">rap</option>
-
# <option value="rock">rock</option>
-
# <option value="country">country</option>
-
# </select>
-
#
-
# * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
-
#
-
# select("post", "category", Post::CATEGORIES, { disabled: 'restricted' })
-
#
-
# could become:
-
#
-
# <select name="post[category]" id="post_category">
-
# <option value="joke">joke</option>
-
# <option value="poem">poem</option>
-
# <option disabled="disabled" value="restricted">restricted</option>
-
# </select>
-
#
-
# When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
-
#
-
# collection_select(:post, :category_id, Category.all, :id, :name, { disabled: -> (category) { category.archived? } })
-
#
-
# If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
-
# <select name="post[category_id]" id="post_category_id">
-
# <option value="1" disabled="disabled">2008 stuff</option>
-
# <option value="2" disabled="disabled">Christmas</option>
-
# <option value="3">Jokes</option>
-
# <option value="4">Poems</option>
-
# </select>
-
#
-
9
module FormOptionsHelper
-
# ERB::Util can mask some helpers like textilize. Make sure to include them.
-
9
include TextHelper
-
-
# Create a select tag and a series of contained option tags for the provided object and method.
-
# The option currently held by the object will be selected, provided that the object is available.
-
#
-
# There are two possible formats for the +choices+ parameter, corresponding to other helpers' output:
-
#
-
# * A flat collection (see +options_for_select+).
-
#
-
# * A nested collection (see +grouped_options_for_select+).
-
#
-
# For example:
-
#
-
# select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: true })
-
#
-
# would become:
-
#
-
# <select name="post[person_id]" id="post_person_id">
-
# <option value="" label=" "></option>
-
# <option value="1" selected="selected">David</option>
-
# <option value="2">Eileen</option>
-
# <option value="3">Rafael</option>
-
# </select>
-
#
-
# assuming the associated person has ID 1.
-
#
-
# This can be used to provide a default set of options in the standard way: before rendering the create form, a
-
# new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
-
# to the database. Instead, a second model object is created when the create request is received.
-
# This allows the user to submit a form page more than once with the expected results of creating multiple records.
-
# In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
-
#
-
# By default, <tt>post.person_id</tt> is the selected option. Specify <tt>selected: value</tt> to use a different selection
-
# or <tt>selected: nil</tt> to leave all options unselected. Similarly, you can specify values to be disabled in the option
-
# tags by specifying the <tt>:disabled</tt> option. This can either be a single value or an array of values to be disabled.
-
#
-
# A block can be passed to +select+ to customize how the options tags will be rendered. This
-
# is useful when the options tag has complex attributes.
-
#
-
# select(report, "campaign_ids") do
-
# available_campaigns.each do |c|
-
# content_tag(:option, c.name, value: c.id, data: { tags: c.tags.to_json })
-
# end
-
# end
-
#
-
# ==== Gotcha
-
#
-
# The HTML specification says when +multiple+ parameter passed to select and all options got deselected
-
# web browsers do not send any value to server. Unfortunately this introduces a gotcha:
-
# if a +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
-
# the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
-
# any mass-assignment idiom like
-
#
-
# @user.update(params[:user])
-
#
-
# wouldn't update roles.
-
#
-
# To prevent this the helper generates an auxiliary hidden field before
-
# every multiple select. The hidden field has the same name as multiple select and blank value.
-
#
-
# <b>Note:</b> The client either sends only the hidden field (representing
-
# the deselected multiple select box), or both fields. This means that the resulting array
-
# always contains a blank string.
-
#
-
# In case if you don't want the helper to generate this hidden field you can specify
-
# <tt>include_hidden: false</tt> option.
-
#
-
9
def select(object, method, choices = nil, options = {}, html_options = {}, &block)
-
Tags::Select.new(object, method, self, choices, options, html_options, &block).render
-
end
-
-
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
-
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
-
# be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
-
# or <tt>:include_blank</tt> in the +options+ hash.
-
#
-
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
-
# of +collection+. The return values are used as the +value+ attribute and contents of each
-
# <tt><option></tt> tag, respectively. They can also be any object that responds to +call+, such
-
# as a +proc+, that will be called for each member of the +collection+ to
-
# retrieve the value/text.
-
#
-
# Example object structure for use with this method:
-
#
-
# class Post < ActiveRecord::Base
-
# belongs_to :author
-
# end
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# def name_with_initial
-
# "#{first_name.first}. #{last_name}"
-
# end
-
# end
-
#
-
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
-
#
-
# collection_select(:post, :author_id, Author.all, :id, :name_with_initial, prompt: true)
-
#
-
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
-
# <select name="post[author_id]" id="post_author_id">
-
# <option value="">Please select</option>
-
# <option value="1" selected="selected">D. Heinemeier Hansson</option>
-
# <option value="2">D. Thomas</option>
-
# <option value="3">M. Clark</option>
-
# </select>
-
9
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
-
Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render
-
end
-
-
# Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
-
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
-
# be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
-
# or <tt>:include_blank</tt> in the +options+ hash.
-
#
-
# Parameters:
-
# * +object+ - The instance of the class to be used for the select tag
-
# * +method+ - The attribute of +object+ corresponding to the select tag
-
# * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
-
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
-
# array of child objects representing the <tt><option></tt> tags. It can also be any object that responds
-
# to +call+, such as a +proc+, that will be called for each member of the +collection+ to retrieve the
-
# value.
-
# * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
-
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag. It can also be any object
-
# that responds to +call+, such as a +proc+, that will be called for each member of the +collection+ to
-
# retrieve the label.
-
# * +option_key_method+ - The name of a method which, when called on a child object of a member of
-
# +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
-
# * +option_value_method+ - The name of a method which, when called on a child object of a member of
-
# +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
-
#
-
# Example object structure for use with this method:
-
#
-
# class Continent < ActiveRecord::Base
-
# has_many :countries
-
# # attribs: id, name
-
# end
-
#
-
# class Country < ActiveRecord::Base
-
# belongs_to :continent
-
# # attribs: id, name, continent_id
-
# end
-
#
-
# class City < ActiveRecord::Base
-
# belongs_to :country
-
# # attribs: id, name, country_id
-
# end
-
#
-
# Sample usage:
-
#
-
# grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name)
-
#
-
# Possible output:
-
#
-
# <select name="city[country_id]" id="city_country_id">
-
# <optgroup label="Africa">
-
# <option value="1">South Africa</option>
-
# <option value="3">Somalia</option>
-
# </optgroup>
-
# <optgroup label="Europe">
-
# <option value="7" selected="selected">Denmark</option>
-
# <option value="2">Ireland</option>
-
# </optgroup>
-
# </select>
-
#
-
9
def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
-
Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
-
end
-
-
# Returns select and option tags for the given object and method, using
-
# #time_zone_options_for_select to generate the list of option tags.
-
#
-
# In addition to the <tt>:include_blank</tt> option documented above,
-
# this method also supports a <tt>:model</tt> option, which defaults
-
# to ActiveSupport::TimeZone. This may be used by users to specify a
-
# different time zone model object. (See +time_zone_options_for_select+
-
# for more information.)
-
#
-
# You can also supply an array of ActiveSupport::TimeZone objects
-
# as +priority_zones+ so that they will be listed above the rest of the
-
# (long) list. You can use ActiveSupport::TimeZone.us_zones for a list
-
# of US time zones, ActiveSupport::TimeZone.country_zones(country_code)
-
# for another country's time zones, or a Regexp to select the zones of
-
# your choice.
-
#
-
# Finally, this method supports a <tt>:default</tt> option, which selects
-
# a default ActiveSupport::TimeZone if the object's time zone is +nil+.
-
#
-
# time_zone_select("user", "time_zone", nil, include_blank: true)
-
#
-
# time_zone_select("user", "time_zone", nil, default: "Pacific Time (US & Canada)")
-
#
-
# time_zone_select("user", 'time_zone', ActiveSupport::TimeZone.us_zones, default: "Pacific Time (US & Canada)")
-
#
-
# time_zone_select("user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ])
-
#
-
# time_zone_select("user", 'time_zone', /Australia/)
-
#
-
# time_zone_select("user", "time_zone", ActiveSupport::TimeZone.all.sort, model: ActiveSupport::TimeZone)
-
9
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
-
Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render
-
end
-
-
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
-
# where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
-
# the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
-
# become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +selected+
-
# may also be an array of values to be selected when using a multiple select.
-
#
-
# options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
-
# # => <option value="$">Dollar</option>
-
# # => <option value="DKK">Kroner</option>
-
#
-
# options_for_select([ "VISA", "MasterCard" ], "MasterCard")
-
# # => <option value="VISA">VISA</option>
-
# # => <option selected="selected" value="MasterCard">MasterCard</option>
-
#
-
# options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
-
# # => <option value="$20">Basic</option>
-
# # => <option value="$40" selected="selected">Plus</option>
-
#
-
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
-
# # => <option selected="selected" value="VISA">VISA</option>
-
# # => <option value="MasterCard">MasterCard</option>
-
# # => <option selected="selected" value="Discover">Discover</option>
-
#
-
# You can optionally provide HTML attributes as the last element of the array.
-
#
-
# options_for_select([ "Denmark", ["USA", { class: 'bold' }], "Sweden" ], ["USA", "Sweden"])
-
# # => <option value="Denmark">Denmark</option>
-
# # => <option value="USA" class="bold" selected="selected">USA</option>
-
# # => <option value="Sweden" selected="selected">Sweden</option>
-
#
-
# options_for_select([["Dollar", "$", { class: "bold" }], ["Kroner", "DKK", { onclick: "alert('HI');" }]])
-
# # => <option value="$" class="bold">Dollar</option>
-
# # => <option value="DKK" onclick="alert('HI');">Kroner</option>
-
#
-
# If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
-
# or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
-
#
-
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: "Super Platinum")
-
# # => <option value="Free">Free</option>
-
# # => <option value="Basic">Basic</option>
-
# # => <option value="Advanced">Advanced</option>
-
# # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
-
#
-
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], disabled: ["Advanced", "Super Platinum"])
-
# # => <option value="Free">Free</option>
-
# # => <option value="Basic">Basic</option>
-
# # => <option value="Advanced" disabled="disabled">Advanced</option>
-
# # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
-
#
-
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], selected: "Free", disabled: "Super Platinum")
-
# # => <option value="Free" selected="selected">Free</option>
-
# # => <option value="Basic">Basic</option>
-
# # => <option value="Advanced">Advanced</option>
-
# # => <option value="Super Platinum" disabled="disabled">Super Platinum</option>
-
#
-
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
-
9
def options_for_select(container, selected = nil)
-
return container if String === container
-
-
selected, disabled = extract_selected_and_disabled(selected).map do |r|
-
Array(r).map(&:to_s)
-
end
-
-
container.map do |element|
-
html_attributes = option_html_attributes(element)
-
text, value = option_text_and_value(element).map(&:to_s)
-
-
html_attributes[:selected] ||= option_value_selected?(value, selected)
-
html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
-
html_attributes[:value] = value
-
-
tag_builder.content_tag_string(:option, text, html_attributes)
-
end.join("\n").html_safe
-
end
-
-
# Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning
-
# the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
-
#
-
# options_from_collection_for_select(@people, 'id', 'name')
-
# # => <option value="#{person.id}">#{person.name}</option>
-
#
-
# This is more often than not used inside a #select_tag like this example:
-
#
-
# select_tag 'person', options_from_collection_for_select(@people, 'id', 'name')
-
#
-
# If +selected+ is specified as a value or array of values, the element(s) returning a match on +value_method+
-
# will be selected option tag(s).
-
#
-
# If +selected+ is specified as a Proc, those members of the collection that return true for the anonymous
-
# function are the selected values.
-
#
-
# +selected+ can also be a hash, specifying both <tt>:selected</tt> and/or <tt>:disabled</tt> values as required.
-
#
-
# Be sure to specify the same class as the +value_method+ when specifying selected or disabled options.
-
# Failure to do this will produce undesired results. Example:
-
# options_from_collection_for_select(@people, 'id', 'name', '1')
-
# Will not select a person with the id of 1 because 1 (an Integer) is not the same as '1' (a string)
-
# options_from_collection_for_select(@people, 'id', 'name', 1)
-
# should produce the desired results.
-
9
def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
-
options = collection.map do |element|
-
[value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element)]
-
end
-
selected, disabled = extract_selected_and_disabled(selected)
-
select_deselect = {
-
selected: extract_values_from_collection(collection, value_method, selected),
-
disabled: extract_values_from_collection(collection, value_method, disabled)
-
}
-
-
options_for_select(options, select_deselect)
-
end
-
-
# Returns a string of <tt><option></tt> tags, like <tt>options_from_collection_for_select</tt>, but
-
# groups them by <tt><optgroup></tt> tags based on the object relationships of the arguments.
-
#
-
# Parameters:
-
# * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
-
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
-
# array of child objects representing the <tt><option></tt> tags.
-
# * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
-
# string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
-
# * +option_key_method+ - The name of a method which, when called on a child object of a member of
-
# +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
-
# * +option_value_method+ - The name of a method which, when called on a child object of a member of
-
# +collection+, returns a value to be used as the contents of its <tt><option></tt> tag.
-
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
-
# which will have the +selected+ attribute set. Corresponds to the return value of one of the calls
-
# to +option_key_method+. If +nil+, no selection is made. Can also be a hash if disabled values are
-
# to be specified.
-
#
-
# Example object structure for use with this method:
-
#
-
# class Continent < ActiveRecord::Base
-
# has_many :countries
-
# # attribs: id, name
-
# end
-
#
-
# class Country < ActiveRecord::Base
-
# belongs_to :continent
-
# # attribs: id, name, continent_id
-
# end
-
#
-
# Sample usage:
-
# option_groups_from_collection_for_select(@continents, :countries, :name, :id, :name, 3)
-
#
-
# Possible output:
-
# <optgroup label="Africa">
-
# <option value="1">Egypt</option>
-
# <option value="4">Rwanda</option>
-
# ...
-
# </optgroup>
-
# <optgroup label="Asia">
-
# <option value="3" selected="selected">China</option>
-
# <option value="12">India</option>
-
# <option value="5">Japan</option>
-
# ...
-
# </optgroup>
-
#
-
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
-
# wrap the output in an appropriate <tt><select></tt> tag.
-
9
def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
-
collection.map do |group|
-
option_tags = options_from_collection_for_select(
-
value_for_collection(group, group_method), option_key_method, option_value_method, selected_key)
-
-
content_tag("optgroup", option_tags, label: value_for_collection(group, group_label_method))
-
end.join.html_safe
-
end
-
-
# Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
-
# wraps them with <tt><optgroup></tt> tags:
-
#
-
# grouped_options = [
-
# ['North America',
-
# [['United States','US'],'Canada']],
-
# ['Europe',
-
# ['Denmark','Germany','France']]
-
# ]
-
# grouped_options_for_select(grouped_options)
-
#
-
# grouped_options = {
-
# 'North America' => [['United States','US'], 'Canada'],
-
# 'Europe' => ['Denmark','Germany','France']
-
# }
-
# grouped_options_for_select(grouped_options)
-
#
-
# Possible output:
-
# <optgroup label="North America">
-
# <option value="US">United States</option>
-
# <option value="Canada">Canada</option>
-
# </optgroup>
-
# <optgroup label="Europe">
-
# <option value="Denmark">Denmark</option>
-
# <option value="Germany">Germany</option>
-
# <option value="France">France</option>
-
# </optgroup>
-
#
-
# Parameters:
-
# * +grouped_options+ - Accepts a nested array or hash of strings. The first value serves as the
-
# <tt><optgroup></tt> label while the second value must be an array of options. The second value can be a
-
# nested array of text-value pairs. See <tt>options_for_select</tt> for more info.
-
# Ex. ["North America",[["United States","US"],["Canada","CA"]]]
-
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
-
# which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
-
# as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
-
#
-
# Options:
-
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
-
# prepends an option with a generic prompt - "Please select" - or the given prompt string.
-
# * <tt>:divider</tt> - the divider for the options groups.
-
#
-
# grouped_options = [
-
# [['United States','US'], 'Canada'],
-
# ['Denmark','Germany','France']
-
# ]
-
# grouped_options_for_select(grouped_options, nil, divider: '---------')
-
#
-
# Possible output:
-
# <optgroup label="---------">
-
# <option value="US">United States</option>
-
# <option value="Canada">Canada</option>
-
# </optgroup>
-
# <optgroup label="---------">
-
# <option value="Denmark">Denmark</option>
-
# <option value="Germany">Germany</option>
-
# <option value="France">France</option>
-
# </optgroup>
-
#
-
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
-
# wrap the output in an appropriate <tt><select></tt> tag.
-
9
def grouped_options_for_select(grouped_options, selected_key = nil, options = {})
-
prompt = options[:prompt]
-
divider = options[:divider]
-
-
body = "".html_safe
-
-
if prompt
-
body.safe_concat content_tag("option", prompt_text(prompt), value: "")
-
end
-
-
grouped_options.each do |container|
-
html_attributes = option_html_attributes(container)
-
-
if divider
-
label = divider
-
else
-
label, container = container
-
end
-
-
html_attributes = { label: label }.merge!(html_attributes)
-
body.safe_concat content_tag("optgroup", options_for_select(container, selected_key), html_attributes)
-
end
-
-
body
-
end
-
-
# Returns a string of option tags for pretty much any time zone in the
-
# world. Supply an ActiveSupport::TimeZone name as +selected+ to have it
-
# marked as the selected option tag. You can also supply an array of
-
# ActiveSupport::TimeZone objects as +priority_zones+, so that they will
-
# be listed above the rest of the (long) list. (You can use
-
# ActiveSupport::TimeZone.us_zones as a convenience for obtaining a list
-
# of the US time zones, or a Regexp to select the zones of your choice)
-
#
-
# The +selected+ parameter must be either +nil+, or a string that names
-
# an ActiveSupport::TimeZone.
-
#
-
# By default, +model+ is the ActiveSupport::TimeZone constant (which can
-
# be obtained in Active Record as a value object). The +model+ parameter
-
# must respond to +all+ and return an array of objects that represent time
-
# zones; each object must respond to +name+. If a Regexp is given it will
-
# attempt to match the zones using <code>match?</code> method.
-
#
-
# NOTE: Only the option tags are returned, you have to wrap this call in
-
# a regular HTML select tag.
-
9
def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
-
zone_options = "".html_safe
-
-
zones = model.all
-
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
-
-
if priority_zones
-
if priority_zones.is_a?(Regexp)
-
priority_zones = zones.select { |z| z.match?(priority_zones) }
-
end
-
-
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
-
zone_options.safe_concat content_tag("option", "-------------", value: "", disabled: true)
-
zone_options.safe_concat "\n"
-
-
zones = zones - priority_zones
-
end
-
-
zone_options.safe_concat options_for_select(convert_zones[zones], selected)
-
end
-
-
# Returns radio button tags for the collection of existing return values
-
# of +method+ for +object+'s class. The value returned from calling
-
# +method+ on the instance +object+ will be selected. If calling +method+
-
# returns +nil+, no selection is made.
-
#
-
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
-
# methods to be called on each member of +collection+. The return values
-
# are used as the +value+ attribute and contents of each radio button tag,
-
# respectively. They can also be any object that responds to +call+, such
-
# as a +proc+, that will be called for each member of the +collection+ to
-
# retrieve the value/text.
-
#
-
# Example object structure for use with this method:
-
# class Post < ActiveRecord::Base
-
# belongs_to :author
-
# end
-
# class Author < ActiveRecord::Base
-
# has_many :posts
-
# def name_with_initial
-
# "#{first_name.first}. #{last_name}"
-
# end
-
# end
-
#
-
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
-
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
-
#
-
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
-
# <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
-
# <label for="post_author_id_1">D. Heinemeier Hansson</label>
-
# <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
-
# <label for="post_author_id_2">D. Thomas</label>
-
# <input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
-
# <label for="post_author_id_3">M. Clark</label>
-
#
-
# It is also possible to customize the way the elements will be shown by
-
# giving a block to the method:
-
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
-
# b.label { b.radio_button }
-
# end
-
#
-
# The argument passed to the block is a special kind of builder for this
-
# collection, which has the ability to generate the label and radio button
-
# for the current item in the collection, with proper text and value.
-
# Using it, you can change the label and radio button display order or
-
# even use the label as wrapper, as in the example above.
-
#
-
# The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept
-
# extra HTML options:
-
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
-
# b.label(class: "radio_button") { b.radio_button(class: "radio_button") }
-
# end
-
#
-
# There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
-
# <tt>value</tt>, which are the current item being rendered, its text and value methods,
-
# respectively. You can use them like this:
-
# collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
-
# b.label(:"data-value" => b.value) { b.radio_button + b.text }
-
# end
-
#
-
# ==== Gotcha
-
#
-
# The HTML specification says when nothing is selected on a collection of radio buttons
-
# web browsers do not send any value to server.
-
# Unfortunately this introduces a gotcha:
-
# if a +User+ model has a +category_id+ field and in the form no category is selected, no +category_id+ parameter is sent. So,
-
# any strong parameters idiom like:
-
#
-
# params.require(:user).permit(...)
-
#
-
# will raise an error since no <tt>{user: ...}</tt> will be present.
-
#
-
# To prevent this the helper generates an auxiliary hidden field before
-
# every collection of radio buttons. The hidden field has the same name as collection radio button and blank value.
-
#
-
# In case if you don't want the helper to generate this hidden field you can specify
-
# <tt>include_hidden: false</tt> option.
-
9
def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
-
Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
-
end
-
-
# Returns check box tags for the collection of existing return values of
-
# +method+ for +object+'s class. The value returned from calling +method+
-
# on the instance +object+ will be selected. If calling +method+ returns
-
# +nil+, no selection is made.
-
#
-
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
-
# methods to be called on each member of +collection+. The return values
-
# are used as the +value+ attribute and contents of each check box tag,
-
# respectively. They can also be any object that responds to +call+, such
-
# as a +proc+, that will be called for each member of the +collection+ to
-
# retrieve the value/text.
-
#
-
# Example object structure for use with this method:
-
# class Post < ActiveRecord::Base
-
# has_and_belongs_to_many :authors
-
# end
-
# class Author < ActiveRecord::Base
-
# has_and_belongs_to_many :posts
-
# def name_with_initial
-
# "#{first_name.first}. #{last_name}"
-
# end
-
# end
-
#
-
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
-
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial)
-
#
-
# If <tt>@post.author_ids</tt> is already <tt>[1]</tt>, this would return:
-
# <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
-
# <label for="post_author_ids_1">D. Heinemeier Hansson</label>
-
# <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
-
# <label for="post_author_ids_2">D. Thomas</label>
-
# <input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
-
# <label for="post_author_ids_3">M. Clark</label>
-
# <input name="post[author_ids][]" type="hidden" value="" />
-
#
-
# It is also possible to customize the way the elements will be shown by
-
# giving a block to the method:
-
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
-
# b.label { b.check_box }
-
# end
-
#
-
# The argument passed to the block is a special kind of builder for this
-
# collection, which has the ability to generate the label and check box
-
# for the current item in the collection, with proper text and value.
-
# Using it, you can change the label and check box display order or even
-
# use the label as wrapper, as in the example above.
-
#
-
# The builder methods <tt>label</tt> and <tt>check_box</tt> also accept
-
# extra HTML options:
-
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
-
# b.label(class: "check_box") { b.check_box(class: "check_box") }
-
# end
-
#
-
# There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
-
# <tt>value</tt>, which are the current item being rendered, its text and value methods,
-
# respectively. You can use them like this:
-
# collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
-
# b.label(:"data-value" => b.value) { b.check_box + b.text }
-
# end
-
#
-
# ==== Gotcha
-
#
-
# When no selection is made for a collection of checkboxes most
-
# web browsers will not send any value.
-
#
-
# For example, if we have a +User+ model with +category_ids+ field and we
-
# have the following code in our update action:
-
#
-
# @user.update(params[:user])
-
#
-
# If no +category_ids+ are selected then we can safely assume this field
-
# will not be updated.
-
#
-
# This is possible thanks to a hidden field generated by the helper method
-
# for every collection of checkboxes.
-
# This hidden field is given the same field name as the checkboxes with a
-
# blank value.
-
#
-
# In the rare case you don't want this hidden field, you can pass the
-
# <tt>include_hidden: false</tt> option to the helper method.
-
9
def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
-
Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
-
end
-
-
9
private
-
9
def option_html_attributes(element)
-
if Array === element
-
element.select { |e| Hash === e }.reduce({}, :merge!)
-
else
-
{}
-
end
-
end
-
-
9
def option_text_and_value(option)
-
# Options are [text, value] pairs or strings used for both.
-
if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
-
option = option.reject { |e| Hash === e } if Array === option
-
[option.first, option.last]
-
else
-
[option, option]
-
end
-
end
-
-
9
def option_value_selected?(value, selected)
-
Array(selected).include? value
-
end
-
-
9
def extract_selected_and_disabled(selected)
-
if selected.is_a?(Proc)
-
[selected, nil]
-
else
-
selected = Array.wrap(selected)
-
options = selected.extract_options!.symbolize_keys
-
selected_items = options.fetch(:selected, selected)
-
[selected_items, options[:disabled]]
-
end
-
end
-
-
9
def extract_values_from_collection(collection, value_method, selected)
-
if selected.is_a?(Proc)
-
collection.map do |element|
-
public_or_deprecated_send(element, value_method) if selected.call(element)
-
end.compact
-
else
-
selected
-
end
-
end
-
-
9
def value_for_collection(item, value)
-
value.respond_to?(:call) ? value.call(item) : public_or_deprecated_send(item, value)
-
end
-
-
9
def public_or_deprecated_send(item, value)
-
item.public_send(value)
-
rescue NoMethodError
-
raise unless item.respond_to?(value, true) && !item.respond_to?(value)
-
ActiveSupport::Deprecation.warn "Using private methods from view helpers is deprecated (calling private #{item.class}##{value})"
-
item.send(value)
-
end
-
-
9
def prompt_text(prompt)
-
prompt.kind_of?(String) ? prompt : I18n.translate("helpers.select.prompt", default: "Please select")
-
end
-
end
-
-
9
class FormBuilder
-
# Wraps ActionView::Helpers::FormOptionsHelper#select for form builders:
-
#
-
# <%= form_for @post do |f| %>
-
# <%= f.select :person_id, Person.all.collect { |p| [ p.name, p.id ] }, include_blank: true %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
9
def select(method, choices = nil, options = {}, html_options = {}, &block)
-
@template.select(@object_name, method, choices, objectify_options(options), @default_html_options.merge(html_options), &block)
-
end
-
-
# Wraps ActionView::Helpers::FormOptionsHelper#collection_select for form builders:
-
#
-
# <%= form_for @post do |f| %>
-
# <%= f.collection_select :person_id, Author.all, :id, :name_with_initial, prompt: true %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
9
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
-
@template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options))
-
end
-
-
# Wraps ActionView::Helpers::FormOptionsHelper#grouped_collection_select for form builders:
-
#
-
# <%= form_for @city do |f| %>
-
# <%= f.grouped_collection_select :country_id, @continents, :countries, :name, :id, :name %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
9
def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
-
@template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_html_options.merge(html_options))
-
end
-
-
# Wraps ActionView::Helpers::FormOptionsHelper#time_zone_select for form builders:
-
#
-
# <%= form_for @user do |f| %>
-
# <%= f.time_zone_select :time_zone, nil, include_blank: true %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
9
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
-
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_html_options.merge(html_options))
-
end
-
-
# Wraps ActionView::Helpers::FormOptionsHelper#collection_check_boxes for form builders:
-
#
-
# <%= form_for @post do |f| %>
-
# <%= f.collection_check_boxes :author_ids, Author.all, :id, :name_with_initial %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
9
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
-
@template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block)
-
end
-
-
# Wraps ActionView::Helpers::FormOptionsHelper#collection_radio_buttons for form builders:
-
#
-
# <%= form_for @post do |f| %>
-
# <%= f.collection_radio_buttons :author_id, Author.all, :id, :name_with_initial %>
-
# <%= f.submit %>
-
# <% end %>
-
#
-
# Please refer to the documentation of the base helper for details.
-
9
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
-
@template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_html_options.merge(html_options), &block)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "cgi"
-
9
require "action_view/helpers/tag_helper"
-
9
require "active_support/core_ext/string/output_safety"
-
9
require "active_support/core_ext/module/attribute_accessors"
-
9
require "active_support/core_ext/symbol/starts_ends_with"
-
-
9
module ActionView
-
# = Action View Form Tag Helpers
-
9
module Helpers #:nodoc:
-
# Provides a number of methods for creating form tags that don't rely on an Active Record object assigned to the template like
-
# FormHelper does. Instead, you provide the names and values manually.
-
#
-
# NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
-
# <tt>disabled: true</tt> will give <tt>disabled="disabled"</tt>.
-
9
module FormTagHelper
-
9
extend ActiveSupport::Concern
-
-
9
include UrlHelper
-
9
include TextHelper
-
-
9
mattr_accessor :embed_authenticity_token_in_remote_forms
-
9
self.embed_authenticity_token_in_remote_forms = nil
-
-
9
mattr_accessor :default_enforce_utf8, default: true
-
-
# Starts a form tag that points the action to a URL configured with <tt>url_for_options</tt> just like
-
# ActionController::Base#url_for. The method for the form defaults to POST.
-
#
-
# ==== Options
-
# * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data".
-
# * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
-
# If "patch", "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt>
-
# is added to simulate the verb over post.
-
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to
-
# pass custom authenticity token string, or to not add authenticity_token field at all
-
# (by passing <tt>false</tt>). Remote forms may omit the embedded authenticity token
-
# by setting <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
-
# This is helpful when you're fragment-caching the form. Remote forms get the
-
# authenticity token from the <tt>meta</tt> tag, so embedding is unnecessary unless you
-
# support browsers without JavaScript.
-
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
-
# submit behavior. By default this behavior is an ajax submit.
-
# * <tt>:enforce_utf8</tt> - If set to false, a hidden input with name utf8 is not output.
-
# * Any other key creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# form_tag('/posts')
-
# # => <form action="/posts" method="post">
-
#
-
# form_tag('/posts/1', method: :put)
-
# # => <form action="/posts/1" method="post"> ... <input name="_method" type="hidden" value="put" /> ...
-
#
-
# form_tag('/upload', multipart: true)
-
# # => <form action="/upload" method="post" enctype="multipart/form-data">
-
#
-
# <%= form_tag('/posts') do -%>
-
# <div><%= submit_tag 'Save' %></div>
-
# <% end -%>
-
# # => <form action="/posts" method="post"><div><input type="submit" name="commit" value="Save" /></div></form>
-
#
-
# <%= form_tag('/posts', remote: true) %>
-
# # => <form action="/posts" method="post" data-remote="true">
-
#
-
# form_tag('http://far.away.com/form', authenticity_token: false)
-
# # form without authenticity token
-
#
-
# form_tag('http://far.away.com/form', authenticity_token: "cf50faa3fe97702ca1ae")
-
# # form with custom authenticity token
-
#
-
9
def form_tag(url_for_options = {}, options = {}, &block)
-
html_options = html_options_for_form(url_for_options, options)
-
if block_given?
-
form_tag_with_body(html_options, capture(&block))
-
else
-
form_tag_html(html_options)
-
end
-
end
-
-
# Creates a dropdown selection box, or if the <tt>:multiple</tt> option is set to true, a multiple
-
# choice selection box.
-
#
-
# Helpers::FormOptions can be used to create common select boxes such as countries, time zones, or
-
# associated records. <tt>option_tags</tt> is a string containing the option tags for the select box.
-
#
-
# ==== Options
-
# * <tt>:multiple</tt> - If set to true, the selection will allow multiple choices.
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:include_blank</tt> - If set to true, an empty option will be created. If set to a string, the string will be used as the option's content and the value will be empty.
-
# * <tt>:prompt</tt> - Create a prompt option with blank value and the text asking user to select something.
-
# * Any other key creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# select_tag "people", options_from_collection_for_select(@people, "id", "name")
-
# # <select id="people" name="people"><option value="1">David</option></select>
-
#
-
# select_tag "people", options_from_collection_for_select(@people, "id", "name", "1")
-
# # <select id="people" name="people"><option value="1" selected="selected">David</option></select>
-
#
-
# select_tag "people", raw("<option>David</option>")
-
# # => <select id="people" name="people"><option>David</option></select>
-
#
-
# select_tag "count", raw("<option>1</option><option>2</option><option>3</option><option>4</option>")
-
# # => <select id="count" name="count"><option>1</option><option>2</option>
-
# # <option>3</option><option>4</option></select>
-
#
-
# select_tag "colors", raw("<option>Red</option><option>Green</option><option>Blue</option>"), multiple: true
-
# # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option>
-
# # <option>Green</option><option>Blue</option></select>
-
#
-
# select_tag "locations", raw("<option>Home</option><option selected='selected'>Work</option><option>Out</option>")
-
# # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
-
# # <option>Out</option></select>
-
#
-
# select_tag "access", raw("<option>Read</option><option>Write</option>"), multiple: true, class: 'form_input', id: 'unique_id'
-
# # => <select class="form_input" id="unique_id" multiple="multiple" name="access[]"><option>Read</option>
-
# # <option>Write</option></select>
-
#
-
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: true
-
# # => <select id="people" name="people"><option value="" label=" "></option><option value="1">David</option></select>
-
#
-
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), include_blank: "All"
-
# # => <select id="people" name="people"><option value="">All</option><option value="1">David</option></select>
-
#
-
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), prompt: "Select something"
-
# # => <select id="people" name="people"><option value="">Select something</option><option value="1">David</option></select>
-
#
-
# select_tag "destination", raw("<option>NYC</option><option>Paris</option><option>Rome</option>"), disabled: true
-
# # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
-
# # <option>Paris</option><option>Rome</option></select>
-
#
-
# select_tag "credit_card", options_for_select([ "VISA", "MasterCard" ], "MasterCard")
-
# # => <select id="credit_card" name="credit_card"><option>VISA</option>
-
# # <option selected="selected">MasterCard</option></select>
-
9
def select_tag(name, option_tags = nil, options = {})
-
option_tags ||= ""
-
html_name = (options[:multiple] == true && !name.end_with?("[]")) ? "#{name}[]" : name
-
-
if options.include?(:include_blank)
-
include_blank = options[:include_blank]
-
options = options.except(:include_blank)
-
options_for_blank_options_tag = { value: "" }
-
-
if include_blank == true
-
include_blank = ""
-
options_for_blank_options_tag[:label] = " "
-
end
-
-
if include_blank
-
option_tags = content_tag("option", include_blank, options_for_blank_options_tag).safe_concat(option_tags)
-
end
-
end
-
-
if prompt = options.delete(:prompt)
-
option_tags = content_tag("option", prompt, value: "").safe_concat(option_tags)
-
end
-
-
content_tag "select", option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
-
end
-
-
# Creates a standard text field; use these text fields to input smaller chunks of text like a username
-
# or a search query.
-
#
-
# ==== Options
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:size</tt> - The number of visible characters that will fit in the input.
-
# * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
-
# * <tt>:placeholder</tt> - The text contained in the field by default which is removed when the field receives focus.
-
# If set to true, use a translation is found in the current I18n locale
-
# (through helpers.placeholders.<modelname>.<attribute>).
-
# * Any other key creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# text_field_tag 'name'
-
# # => <input id="name" name="name" type="text" />
-
#
-
# text_field_tag 'query', 'Enter your search query here'
-
# # => <input id="query" name="query" type="text" value="Enter your search query here" />
-
#
-
# text_field_tag 'search', nil, placeholder: 'Enter search term...'
-
# # => <input id="search" name="search" placeholder="Enter search term..." type="text" />
-
#
-
# text_field_tag 'request', nil, class: 'special_input'
-
# # => <input class="special_input" id="request" name="request" type="text" />
-
#
-
# text_field_tag 'address', '', size: 75
-
# # => <input id="address" name="address" size="75" type="text" value="" />
-
#
-
# text_field_tag 'zip', nil, maxlength: 5
-
# # => <input id="zip" maxlength="5" name="zip" type="text" />
-
#
-
# text_field_tag 'payment_amount', '$0.00', disabled: true
-
# # => <input disabled="disabled" id="payment_amount" name="payment_amount" type="text" value="$0.00" />
-
#
-
# text_field_tag 'ip', '0.0.0.0', maxlength: 15, size: 20, class: "ip-input"
-
# # => <input class="ip-input" id="ip" maxlength="15" name="ip" size="20" type="text" value="0.0.0.0" />
-
9
def text_field_tag(name, value = nil, options = {})
-
tag :input, { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
-
end
-
-
# Creates a label element. Accepts a block.
-
#
-
# ==== Options
-
# * Creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# label_tag 'name'
-
# # => <label for="name">Name</label>
-
#
-
# label_tag 'name', 'Your name'
-
# # => <label for="name">Your name</label>
-
#
-
# label_tag 'name', nil, class: 'small_label'
-
# # => <label for="name" class="small_label">Name</label>
-
9
def label_tag(name = nil, content_or_options = nil, options = nil, &block)
-
if block_given? && content_or_options.is_a?(Hash)
-
options = content_or_options = content_or_options.stringify_keys
-
else
-
options ||= {}
-
options = options.stringify_keys
-
end
-
options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
-
content_tag :label, content_or_options || name.to_s.humanize, options, &block
-
end
-
-
# Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or
-
# data that should be hidden from the user.
-
#
-
# ==== Options
-
# * Creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# hidden_field_tag 'tags_list'
-
# # => <input id="tags_list" name="tags_list" type="hidden" />
-
#
-
# hidden_field_tag 'token', 'VUBJKB23UIVI1UU1VOBVI@'
-
# # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />
-
#
-
# hidden_field_tag 'collected_input', '', onchange: "alert('Input collected!')"
-
# # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
-
# # type="hidden" value="" />
-
9
def hidden_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :hidden))
-
end
-
-
# Creates a file upload field. If you are using file uploads then you will also need
-
# to set the multipart option for the form tag:
-
#
-
# <%= form_tag '/upload', multipart: true do %>
-
# <label for="file">File to Upload</label> <%= file_field_tag "file" %>
-
# <%= submit_tag %>
-
# <% end %>
-
#
-
# The specified URL will then be passed a File object containing the selected file, or if the field
-
# was left blank, a StringIO object.
-
#
-
# ==== Options
-
# * Creates standard HTML attributes for the tag.
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files.
-
# * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations.
-
#
-
# ==== Examples
-
# file_field_tag 'attachment'
-
# # => <input id="attachment" name="attachment" type="file" />
-
#
-
# file_field_tag 'avatar', class: 'profile_input'
-
# # => <input class="profile_input" id="avatar" name="avatar" type="file" />
-
#
-
# file_field_tag 'picture', disabled: true
-
# # => <input disabled="disabled" id="picture" name="picture" type="file" />
-
#
-
# file_field_tag 'resume', value: '~/resume.doc'
-
# # => <input id="resume" name="resume" type="file" value="~/resume.doc" />
-
#
-
# file_field_tag 'user_pic', accept: 'image/png,image/gif,image/jpeg'
-
# # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
-
#
-
# file_field_tag 'file', accept: 'text/html', class: 'upload', value: 'index.html'
-
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
-
9
def file_field_tag(name, options = {})
-
text_field_tag(name, nil, convert_direct_upload_option_to_url(options.merge(type: :file)))
-
end
-
-
# Creates a password field, a masked text field that will hide the users input behind a mask character.
-
#
-
# ==== Options
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:size</tt> - The number of visible characters that will fit in the input.
-
# * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
-
# * Any other key creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# password_field_tag 'pass'
-
# # => <input id="pass" name="pass" type="password" />
-
#
-
# password_field_tag 'secret', 'Your secret here'
-
# # => <input id="secret" name="secret" type="password" value="Your secret here" />
-
#
-
# password_field_tag 'masked', nil, class: 'masked_input_field'
-
# # => <input class="masked_input_field" id="masked" name="masked" type="password" />
-
#
-
# password_field_tag 'token', '', size: 15
-
# # => <input id="token" name="token" size="15" type="password" value="" />
-
#
-
# password_field_tag 'key', nil, maxlength: 16
-
# # => <input id="key" maxlength="16" name="key" type="password" />
-
#
-
# password_field_tag 'confirm_pass', nil, disabled: true
-
# # => <input disabled="disabled" id="confirm_pass" name="confirm_pass" type="password" />
-
#
-
# password_field_tag 'pin', '1234', maxlength: 4, size: 6, class: "pin_input"
-
# # => <input class="pin_input" id="pin" maxlength="4" name="pin" size="6" type="password" value="1234" />
-
9
def password_field_tag(name = "password", value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :password))
-
end
-
-
# Creates a text input area; use a textarea for longer text inputs such as blog posts or descriptions.
-
#
-
# ==== Options
-
# * <tt>:size</tt> - A string specifying the dimensions (columns by rows) of the textarea (e.g., "25x10").
-
# * <tt>:rows</tt> - Specify the number of rows in the textarea
-
# * <tt>:cols</tt> - Specify the number of columns in the textarea
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * <tt>:escape</tt> - By default, the contents of the text input are HTML escaped.
-
# If you need unescaped contents, set this to false.
-
# * Any other key creates standard HTML attributes for the tag.
-
#
-
# ==== Examples
-
# text_area_tag 'post'
-
# # => <textarea id="post" name="post"></textarea>
-
#
-
# text_area_tag 'bio', @user.bio
-
# # => <textarea id="bio" name="bio">This is my biography.</textarea>
-
#
-
# text_area_tag 'body', nil, rows: 10, cols: 25
-
# # => <textarea cols="25" id="body" name="body" rows="10"></textarea>
-
#
-
# text_area_tag 'body', nil, size: "25x10"
-
# # => <textarea name="body" id="body" cols="25" rows="10"></textarea>
-
#
-
# text_area_tag 'description', "Description goes here.", disabled: true
-
# # => <textarea disabled="disabled" id="description" name="description">Description goes here.</textarea>
-
#
-
# text_area_tag 'comment', nil, class: 'comment_input'
-
# # => <textarea class="comment_input" id="comment" name="comment"></textarea>
-
9
def text_area_tag(name, content = nil, options = {})
-
options = options.stringify_keys
-
-
if size = options.delete("size")
-
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
-
end
-
-
escape = options.delete("escape") { true }
-
content = ERB::Util.html_escape(content) if escape
-
-
content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
-
end
-
-
# Creates a check box form input tag.
-
#
-
# ==== Options
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * Any other key creates standard HTML options for the tag.
-
#
-
# ==== Examples
-
# check_box_tag 'accept'
-
# # => <input id="accept" name="accept" type="checkbox" value="1" />
-
#
-
# check_box_tag 'rock', 'rock music'
-
# # => <input id="rock" name="rock" type="checkbox" value="rock music" />
-
#
-
# check_box_tag 'receive_email', 'yes', true
-
# # => <input checked="checked" id="receive_email" name="receive_email" type="checkbox" value="yes" />
-
#
-
# check_box_tag 'tos', 'yes', false, class: 'accept_tos'
-
# # => <input class="accept_tos" id="tos" name="tos" type="checkbox" value="yes" />
-
#
-
# check_box_tag 'eula', 'accepted', false, disabled: true
-
# # => <input disabled="disabled" id="eula" name="eula" type="checkbox" value="accepted" />
-
9
def check_box_tag(name, value = "1", checked = false, options = {})
-
html_options = { "type" => "checkbox", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys)
-
html_options["checked"] = "checked" if checked
-
tag :input, html_options
-
end
-
-
# Creates a radio button; use groups of radio buttons named the same to allow users to
-
# select from a group of options.
-
#
-
# ==== Options
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * Any other key creates standard HTML options for the tag.
-
#
-
# ==== Examples
-
# radio_button_tag 'favorite_color', 'maroon'
-
# # => <input id="favorite_color_maroon" name="favorite_color" type="radio" value="maroon" />
-
#
-
# radio_button_tag 'receive_updates', 'no', true
-
# # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
-
#
-
# radio_button_tag 'time_slot', "3:00 p.m.", false, disabled: true
-
# # => <input disabled="disabled" id="time_slot_3:00_p.m." name="time_slot" type="radio" value="3:00 p.m." />
-
#
-
# radio_button_tag 'color', "green", true, class: "color_input"
-
# # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" />
-
9
def radio_button_tag(name, value, checked = false, options = {})
-
html_options = { "type" => "radio", "name" => name, "id" => "#{sanitize_to_id(name)}_#{sanitize_to_id(value)}", "value" => value }.update(options.stringify_keys)
-
html_options["checked"] = "checked" if checked
-
tag :input, html_options
-
end
-
-
# Creates a submit button with the text <tt>value</tt> as the caption.
-
#
-
# ==== Options
-
# * <tt>:data</tt> - This option can be used to add custom data attributes.
-
# * <tt>:disabled</tt> - If true, the user will not be able to use this input.
-
# * Any other key creates standard HTML options for the tag.
-
#
-
# ==== Data attributes
-
#
-
# * <tt>confirm: 'question?'</tt> - If present the unobtrusive JavaScript
-
# drivers will provide a prompt with the question specified. If the user accepts,
-
# the form is processed normally, otherwise no action is taken.
-
# * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a
-
# disabled version of the submit button when the form is submitted. This feature is
-
# provided by the unobtrusive JavaScript driver. To disable this feature for a single submit tag
-
# pass <tt>:data => { disable_with: false }</tt> Defaults to value attribute.
-
#
-
# ==== Examples
-
# submit_tag
-
# # => <input name="commit" data-disable-with="Save changes" type="submit" value="Save changes" />
-
#
-
# submit_tag "Edit this article"
-
# # => <input name="commit" data-disable-with="Edit this article" type="submit" value="Edit this article" />
-
#
-
# submit_tag "Save edits", disabled: true
-
# # => <input disabled="disabled" name="commit" data-disable-with="Save edits" type="submit" value="Save edits" />
-
#
-
# submit_tag "Complete sale", data: { disable_with: "Submitting..." }
-
# # => <input name="commit" data-disable-with="Submitting..." type="submit" value="Complete sale" />
-
#
-
# submit_tag nil, class: "form_submit"
-
# # => <input class="form_submit" name="commit" type="submit" />
-
#
-
# submit_tag "Edit", class: "edit_button"
-
# # => <input class="edit_button" data-disable-with="Edit" name="commit" type="submit" value="Edit" />
-
#
-
# submit_tag "Save", data: { confirm: "Are you sure?" }
-
# # => <input name='commit' type='submit' value='Save' data-disable-with="Save" data-confirm="Are you sure?" />
-
#
-
9
def submit_tag(value = "Save changes", options = {})
-
options = options.deep_stringify_keys
-
tag_options = { "type" => "submit", "name" => "commit", "value" => value }.update(options)
-
set_default_disable_with value, tag_options
-
tag :input, tag_options
-
end
-
-
# Creates a button element that defines a <tt>submit</tt> button,
-
# <tt>reset</tt> button or a generic button which can be used in
-
# JavaScript, for example. You can use the button tag as a regular
-
# submit tag but it isn't supported in legacy browsers. However,
-
# the button tag does allow for richer labels such as images and emphasis,
-
# so this helper will also accept a block. By default, it will create
-
# a button tag with type <tt>submit</tt>, if type is not given.
-
#
-
# ==== Options
-
# * <tt>:data</tt> - This option can be used to add custom data attributes.
-
# * <tt>:disabled</tt> - If true, the user will not be able to
-
# use this input.
-
# * Any other key creates standard HTML options for the tag.
-
#
-
# ==== Data attributes
-
#
-
# * <tt>confirm: 'question?'</tt> - If present, the
-
# unobtrusive JavaScript drivers will provide a prompt with
-
# the question specified. If the user accepts, the form is
-
# processed normally, otherwise no action is taken.
-
# * <tt>:disable_with</tt> - Value of this parameter will be
-
# used as the value for a disabled version of the submit
-
# button when the form is submitted. This feature is provided
-
# by the unobtrusive JavaScript driver.
-
#
-
# ==== Examples
-
# button_tag
-
# # => <button name="button" type="submit">Button</button>
-
#
-
# button_tag 'Reset', type: 'reset'
-
# # => <button name="button" type="reset">Reset</button>
-
#
-
# button_tag 'Button', type: 'button'
-
# # => <button name="button" type="button">Button</button>
-
#
-
# button_tag 'Reset', type: 'reset', disabled: true
-
# # => <button name="button" type="reset" disabled="disabled">Reset</button>
-
#
-
# button_tag(type: 'button') do
-
# content_tag(:strong, 'Ask me!')
-
# end
-
# # => <button name="button" type="button">
-
# # <strong>Ask me!</strong>
-
# # </button>
-
#
-
# button_tag "Save", data: { confirm: "Are you sure?" }
-
# # => <button name="button" type="submit" data-confirm="Are you sure?">Save</button>
-
#
-
# button_tag "Checkout", data: { disable_with: "Please wait..." }
-
# # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button>
-
#
-
9
def button_tag(content_or_options = nil, options = nil, &block)
-
if content_or_options.is_a? Hash
-
options = content_or_options
-
else
-
options ||= {}
-
end
-
-
options = { "name" => "button", "type" => "submit" }.merge!(options.stringify_keys)
-
-
if block_given?
-
content_tag :button, options, &block
-
else
-
content_tag :button, content_or_options || "Button", options
-
end
-
end
-
-
# Displays an image which when clicked will submit the form.
-
#
-
# <tt>source</tt> is passed to AssetTagHelper#path_to_image
-
#
-
# ==== Options
-
# * <tt>:data</tt> - This option can be used to add custom data attributes.
-
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
-
# * Any other key creates standard HTML options for the tag.
-
#
-
# ==== Data attributes
-
#
-
# * <tt>confirm: 'question?'</tt> - This will add a JavaScript confirm
-
# prompt with the question specified. If the user accepts, the form is
-
# processed normally, otherwise no action is taken.
-
#
-
# ==== Examples
-
# image_submit_tag("login.png")
-
# # => <input src="/assets/login.png" type="image" />
-
#
-
# image_submit_tag("purchase.png", disabled: true)
-
# # => <input disabled="disabled" src="/assets/purchase.png" type="image" />
-
#
-
# image_submit_tag("search.png", class: 'search_button', alt: 'Find')
-
# # => <input class="search_button" src="/assets/search.png" type="image" />
-
#
-
# image_submit_tag("agree.png", disabled: true, class: "agree_disagree_button")
-
# # => <input class="agree_disagree_button" disabled="disabled" src="/assets/agree.png" type="image" />
-
#
-
# image_submit_tag("save.png", data: { confirm: "Are you sure?" })
-
# # => <input src="/assets/save.png" data-confirm="Are you sure?" type="image" />
-
9
def image_submit_tag(source, options = {})
-
options = options.stringify_keys
-
src = path_to_image(source, skip_pipeline: options.delete("skip_pipeline"))
-
tag :input, { "type" => "image", "src" => src }.update(options)
-
end
-
-
# Creates a field set for grouping HTML form elements.
-
#
-
# <tt>legend</tt> will become the fieldset's title (optional as per W3C).
-
# <tt>options</tt> accept the same values as tag.
-
#
-
# ==== Examples
-
# <%= field_set_tag do %>
-
# <p><%= text_field_tag 'name' %></p>
-
# <% end %>
-
# # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>
-
#
-
# <%= field_set_tag 'Your details' do %>
-
# <p><%= text_field_tag 'name' %></p>
-
# <% end %>
-
# # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
-
#
-
# <%= field_set_tag nil, class: 'format' do %>
-
# <p><%= text_field_tag 'name' %></p>
-
# <% end %>
-
# # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
-
9
def field_set_tag(legend = nil, options = nil, &block)
-
output = tag(:fieldset, options, true)
-
output.safe_concat(content_tag("legend", legend)) unless legend.blank?
-
output.concat(capture(&block)) if block_given?
-
output.safe_concat("</fieldset>")
-
end
-
-
# Creates a text field of type "color".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
#
-
# ==== Examples
-
# color_field_tag 'name'
-
# # => <input id="name" name="name" type="color" />
-
#
-
# color_field_tag 'color', '#DEF726'
-
# # => <input id="color" name="color" type="color" value="#DEF726" />
-
#
-
# color_field_tag 'color', nil, class: 'special_input'
-
# # => <input class="special_input" id="color" name="color" type="color" />
-
#
-
# color_field_tag 'color', '#DEF726', class: 'special_input', disabled: true
-
# # => <input disabled="disabled" class="special_input" id="color" name="color" type="color" value="#DEF726" />
-
9
def color_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :color))
-
end
-
-
# Creates a text field of type "search".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
#
-
# ==== Examples
-
# search_field_tag 'name'
-
# # => <input id="name" name="name" type="search" />
-
#
-
# search_field_tag 'search', 'Enter your search query here'
-
# # => <input id="search" name="search" type="search" value="Enter your search query here" />
-
#
-
# search_field_tag 'search', nil, class: 'special_input'
-
# # => <input class="special_input" id="search" name="search" type="search" />
-
#
-
# search_field_tag 'search', 'Enter your search query here', class: 'special_input', disabled: true
-
# # => <input disabled="disabled" class="special_input" id="search" name="search" type="search" value="Enter your search query here" />
-
9
def search_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :search))
-
end
-
-
# Creates a text field of type "tel".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
#
-
# ==== Examples
-
# telephone_field_tag 'name'
-
# # => <input id="name" name="name" type="tel" />
-
#
-
# telephone_field_tag 'tel', '0123456789'
-
# # => <input id="tel" name="tel" type="tel" value="0123456789" />
-
#
-
# telephone_field_tag 'tel', nil, class: 'special_input'
-
# # => <input class="special_input" id="tel" name="tel" type="tel" />
-
#
-
# telephone_field_tag 'tel', '0123456789', class: 'special_input', disabled: true
-
# # => <input disabled="disabled" class="special_input" id="tel" name="tel" type="tel" value="0123456789" />
-
9
def telephone_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :tel))
-
end
-
9
alias phone_field_tag telephone_field_tag
-
-
# Creates a text field of type "date".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
#
-
# ==== Examples
-
# date_field_tag 'name'
-
# # => <input id="name" name="name" type="date" />
-
#
-
# date_field_tag 'date', '01/01/2014'
-
# # => <input id="date" name="date" type="date" value="01/01/2014" />
-
#
-
# date_field_tag 'date', nil, class: 'special_input'
-
# # => <input class="special_input" id="date" name="date" type="date" />
-
#
-
# date_field_tag 'date', '01/01/2014', class: 'special_input', disabled: true
-
# # => <input disabled="disabled" class="special_input" id="date" name="date" type="date" value="01/01/2014" />
-
9
def date_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :date))
-
end
-
-
# Creates a text field of type "time".
-
#
-
# === Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
9
def time_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :time))
-
end
-
-
# Creates a text field of type "datetime-local".
-
#
-
# === Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
9
def datetime_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: "datetime-local"))
-
end
-
-
9
alias datetime_local_field_tag datetime_field_tag
-
-
# Creates a text field of type "month".
-
#
-
# === Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
9
def month_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :month))
-
end
-
-
# Creates a text field of type "week".
-
#
-
# === Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
9
def week_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :week))
-
end
-
-
# Creates a text field of type "url".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
#
-
# ==== Examples
-
# url_field_tag 'name'
-
# # => <input id="name" name="name" type="url" />
-
#
-
# url_field_tag 'url', 'http://rubyonrails.org'
-
# # => <input id="url" name="url" type="url" value="http://rubyonrails.org" />
-
#
-
# url_field_tag 'url', nil, class: 'special_input'
-
# # => <input class="special_input" id="url" name="url" type="url" />
-
#
-
# url_field_tag 'url', 'http://rubyonrails.org', class: 'special_input', disabled: true
-
# # => <input disabled="disabled" class="special_input" id="url" name="url" type="url" value="http://rubyonrails.org" />
-
9
def url_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :url))
-
end
-
-
# Creates a text field of type "email".
-
#
-
# ==== Options
-
# * Accepts the same options as text_field_tag.
-
#
-
# ==== Examples
-
# email_field_tag 'name'
-
# # => <input id="name" name="name" type="email" />
-
#
-
# email_field_tag 'email', 'email@example.com'
-
# # => <input id="email" name="email" type="email" value="email@example.com" />
-
#
-
# email_field_tag 'email', nil, class: 'special_input'
-
# # => <input class="special_input" id="email" name="email" type="email" />
-
#
-
# email_field_tag 'email', 'email@example.com', class: 'special_input', disabled: true
-
# # => <input disabled="disabled" class="special_input" id="email" name="email" type="email" value="email@example.com" />
-
9
def email_field_tag(name, value = nil, options = {})
-
text_field_tag(name, value, options.merge(type: :email))
-
end
-
-
# Creates a number field.
-
#
-
# ==== Options
-
# * <tt>:min</tt> - The minimum acceptable value.
-
# * <tt>:max</tt> - The maximum acceptable value.
-
# * <tt>:in</tt> - A range specifying the <tt>:min</tt> and
-
# <tt>:max</tt> values.
-
# * <tt>:within</tt> - Same as <tt>:in</tt>.
-
# * <tt>:step</tt> - The acceptable value granularity.
-
# * Otherwise accepts the same options as text_field_tag.
-
#
-
# ==== Examples
-
# number_field_tag 'quantity'
-
# # => <input id="quantity" name="quantity" type="number" />
-
#
-
# number_field_tag 'quantity', '1'
-
# # => <input id="quantity" name="quantity" type="number" value="1" />
-
#
-
# number_field_tag 'quantity', nil, class: 'special_input'
-
# # => <input class="special_input" id="quantity" name="quantity" type="number" />
-
#
-
# number_field_tag 'quantity', nil, min: 1
-
# # => <input id="quantity" name="quantity" min="1" type="number" />
-
#
-
# number_field_tag 'quantity', nil, max: 9
-
# # => <input id="quantity" name="quantity" max="9" type="number" />
-
#
-
# number_field_tag 'quantity', nil, in: 1...10
-
# # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
-
#
-
# number_field_tag 'quantity', nil, within: 1...10
-
# # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
-
#
-
# number_field_tag 'quantity', nil, min: 1, max: 10
-
# # => <input id="quantity" name="quantity" min="1" max="10" type="number" />
-
#
-
# number_field_tag 'quantity', nil, min: 1, max: 10, step: 2
-
# # => <input id="quantity" name="quantity" min="1" max="10" step="2" type="number" />
-
#
-
# number_field_tag 'quantity', '1', class: 'special_input', disabled: true
-
# # => <input disabled="disabled" class="special_input" id="quantity" name="quantity" type="number" value="1" />
-
9
def number_field_tag(name, value = nil, options = {})
-
options = options.stringify_keys
-
options["type"] ||= "number"
-
if range = options.delete("in") || options.delete("within")
-
options.update("min" => range.min, "max" => range.max)
-
end
-
text_field_tag(name, value, options)
-
end
-
-
# Creates a range form element.
-
#
-
# ==== Options
-
# * Accepts the same options as number_field_tag.
-
9
def range_field_tag(name, value = nil, options = {})
-
number_field_tag(name, value, options.merge(type: :range))
-
end
-
-
# Creates the hidden UTF8 enforcer tag. Override this method in a helper
-
# to customize the tag.
-
9
def utf8_enforcer_tag
-
# Use raw HTML to ensure the value is written as an HTML entity; it
-
# needs to be the right character regardless of which encoding the
-
# browser infers.
-
'<input name="utf8" type="hidden" value="✓" />'.html_safe
-
end
-
-
9
private
-
9
def html_options_for_form(url_for_options, options)
-
options.stringify_keys.tap do |html_options|
-
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
-
# The following URL is unescaped, this is just a hash of options, and it is the
-
# responsibility of the caller to escape all the values.
-
html_options["action"] = url_for(url_for_options)
-
html_options["accept-charset"] = "UTF-8"
-
-
html_options["data-remote"] = true if html_options.delete("remote")
-
-
if html_options["data-remote"] &&
-
!embed_authenticity_token_in_remote_forms &&
-
html_options["authenticity_token"].blank?
-
# The authenticity token is taken from the meta tag in this case
-
html_options["authenticity_token"] = false
-
elsif html_options["authenticity_token"] == true
-
# Include the default authenticity_token, which is only generated when its set to nil,
-
# but we needed the true value to override the default of no authenticity_token on data-remote.
-
html_options["authenticity_token"] = nil
-
end
-
end
-
end
-
-
9
def extra_tags_for_form(html_options)
-
authenticity_token = html_options.delete("authenticity_token")
-
method = html_options.delete("method").to_s.downcase
-
-
method_tag = \
-
case method
-
when "get"
-
html_options["method"] = "get"
-
""
-
when "post", ""
-
html_options["method"] = "post"
-
token_tag(authenticity_token, form_options: {
-
action: html_options["action"],
-
method: "post"
-
})
-
else
-
html_options["method"] = "post"
-
method_tag(method) + token_tag(authenticity_token, form_options: {
-
action: html_options["action"],
-
method: method
-
})
-
end
-
-
if html_options.delete("enforce_utf8") { default_enforce_utf8 }
-
utf8_enforcer_tag + method_tag
-
else
-
method_tag
-
end
-
end
-
-
9
def form_tag_html(html_options)
-
extra_tags = extra_tags_for_form(html_options)
-
tag(:form, html_options, true) + extra_tags
-
end
-
-
9
def form_tag_with_body(html_options, content)
-
output = form_tag_html(html_options)
-
output << content
-
output.safe_concat("</form>")
-
end
-
-
# see http://www.w3.org/TR/html4/types.html#type-name
-
9
def sanitize_to_id(name)
-
name.to_s.delete("]").tr("^-a-zA-Z0-9:.", "_")
-
end
-
-
9
def set_default_disable_with(value, tag_options)
-
return unless ActionView::Base.automatically_disable_submit_tag
-
data = tag_options["data"]
-
-
unless tag_options["data-disable-with"] == false || (data && data["disable_with"] == false)
-
disable_with_text = tag_options["data-disable-with"]
-
disable_with_text ||= data["disable_with"] if data
-
disable_with_text ||= value.to_s.clone
-
tag_options.deep_merge!("data" => { "disable_with" => disable_with_text })
-
else
-
data.delete("disable_with") if data
-
end
-
-
tag_options.delete("data-disable-with")
-
end
-
-
9
def convert_direct_upload_option_to_url(options)
-
if options.delete(:direct_upload) && respond_to?(:rails_direct_uploads_url)
-
options["data-direct-upload-url"] = rails_direct_uploads_url
-
end
-
options
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "action_view/helpers/tag_helper"
-
-
9
module ActionView
-
9
module Helpers #:nodoc:
-
9
module JavaScriptHelper
-
9
JS_ESCAPE_MAP = {
-
'\\' => '\\\\',
-
"</" => '<\/',
-
"\r\n" => '\n',
-
"\n" => '\n',
-
"\r" => '\n',
-
'"' => '\\"',
-
"'" => "\\'",
-
"`" => "\\`",
-
"$" => "\\$"
-
}
-
-
9
JS_ESCAPE_MAP[(+"\342\200\250").force_encoding(Encoding::UTF_8).encode!] = "
"
-
9
JS_ESCAPE_MAP[(+"\342\200\251").force_encoding(Encoding::UTF_8).encode!] = "
"
-
-
# Escapes carriage returns and single and double quotes for JavaScript segments.
-
#
-
# Also available through the alias j(). This is particularly helpful in JavaScript
-
# responses, like:
-
#
-
# $('some_element').replaceWith('<%= j render 'some/element_template' %>');
-
9
def escape_javascript(javascript)
-
javascript = javascript.to_s
-
if javascript.empty?
-
result = ""
-
else
-
result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"']|[`]|[$])/u, JS_ESCAPE_MAP)
-
end
-
javascript.html_safe? ? result.html_safe : result
-
end
-
-
9
alias_method :j, :escape_javascript
-
-
# Returns a JavaScript tag with the +content+ inside. Example:
-
# javascript_tag "alert('All is good')"
-
#
-
# Returns:
-
# <script>
-
# //<![CDATA[
-
# alert('All is good')
-
# //]]>
-
# </script>
-
#
-
# +html_options+ may be a hash of attributes for the <tt>\<script></tt>
-
# tag.
-
#
-
# javascript_tag "alert('All is good')", type: 'application/javascript'
-
#
-
# Returns:
-
# <script type="application/javascript">
-
# //<![CDATA[
-
# alert('All is good')
-
# //]]>
-
# </script>
-
#
-
# Instead of passing the content as an argument, you can also use a block
-
# in which case, you pass your +html_options+ as the first parameter.
-
#
-
# <%= javascript_tag type: 'application/javascript' do -%>
-
# alert('All is good')
-
# <% end -%>
-
#
-
# If you have a content security policy enabled then you can add an automatic
-
# nonce value by passing <tt>nonce: true</tt> as part of +html_options+. Example:
-
#
-
# <%= javascript_tag nonce: true do -%>
-
# alert('All is good')
-
# <% end -%>
-
9
def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
-
content =
-
if block_given?
-
html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
-
capture(&block)
-
else
-
content_or_options_with_block
-
end
-
-
if html_options[:nonce] == true
-
html_options[:nonce] = content_security_policy_nonce
-
end
-
-
content_tag("script", javascript_cdata_section(content), html_options)
-
end
-
-
9
def javascript_cdata_section(content) #:nodoc:
-
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "active_support/core_ext/hash/keys"
-
9
require "active_support/core_ext/string/output_safety"
-
9
require "active_support/number_helper"
-
-
9
module ActionView
-
# = Action View Number Helpers
-
9
module Helpers #:nodoc:
-
# Provides methods for converting numbers into formatted strings.
-
# Methods are provided for phone numbers, currency, percentage,
-
# precision, positional notation, file size and pretty printing.
-
#
-
# Most methods expect a +number+ argument, and will return it
-
# unchanged if can't be converted into a valid number.
-
9
module NumberHelper
-
# Raised when argument +number+ param given to the helpers is invalid and
-
# the option :raise is set to +true+.
-
9
class InvalidNumberError < StandardError
-
9
attr_accessor :number
-
9
def initialize(number)
-
@number = number
-
end
-
end
-
-
# Formats a +number+ into a phone number (US by default e.g., (555)
-
# 123-9876). You can customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:area_code</tt> - Adds parentheses around the area code.
-
# * <tt>:delimiter</tt> - Specifies the delimiter to use
-
# (defaults to "-").
-
# * <tt>:extension</tt> - Specifies an extension to add to the
-
# end of the generated number.
-
# * <tt>:country_code</tt> - Sets the country code for the phone
-
# number.
-
# * <tt>:pattern</tt> - Specifies how the number is divided into three
-
# groups with the custom regexp to override the default format.
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_to_phone(5551234) # => 555-1234
-
# number_to_phone("5551234") # => 555-1234
-
# number_to_phone(1235551234) # => 123-555-1234
-
# number_to_phone(1235551234, area_code: true) # => (123) 555-1234
-
# number_to_phone(1235551234, delimiter: " ") # => 123 555 1234
-
# number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
-
# number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
-
# number_to_phone("123a456") # => 123a456
-
# number_to_phone("1234a567", raise: true) # => InvalidNumberError
-
#
-
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".")
-
# # => +1.123.555.1234 x 1343
-
#
-
# number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
-
# # => "(755) 6123-4567"
-
# number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)
-
# # => "133-1234-5678"
-
9
def number_to_phone(number, options = {})
-
return unless number
-
options = options.symbolize_keys
-
-
parse_float(number, true) if options.delete(:raise)
-
ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options))
-
end
-
-
# Formats a +number+ into a currency string (e.g., $13.65). You
-
# can customize the format in the +options+ hash.
-
#
-
# The currency unit and number formatting of the current locale will be used
-
# unless otherwise specified in the provided options. No currency conversion
-
# is performed. If the user is given a way to change their locale, they will
-
# also be able to change the relative value of the currency displayed with
-
# this helper. If your application will ever support multiple locales, you
-
# may want to specify a constant <tt>:locale</tt> option or consider
-
# using a library capable of currency conversion.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the level of precision (defaults
-
# to 2).
-
# * <tt>:unit</tt> - Sets the denomination of the currency
-
# (defaults to "$").
-
# * <tt>:separator</tt> - Sets the separator between the units
-
# (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to ",").
-
# * <tt>:format</tt> - Sets the format for non-negative numbers
-
# (defaults to "%u%n"). Fields are <tt>%u</tt> for the
-
# currency, and <tt>%n</tt> for the number.
-
# * <tt>:negative_format</tt> - Sets the format for negative
-
# numbers (defaults to prepending a hyphen to the formatted
-
# number given by <tt>:format</tt>). Accepts the same fields
-
# than <tt>:format</tt>, except <tt>%n</tt> is here the
-
# absolute value of the number.
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +false+).
-
#
-
# ==== Examples
-
#
-
# number_to_currency(1234567890.50) # => $1,234,567,890.50
-
# number_to_currency(1234567890.506) # => $1,234,567,890.51
-
# number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
-
# number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
-
# number_to_currency("123a456") # => $123a456
-
#
-
# number_to_currency("123a456", raise: true) # => InvalidNumberError
-
#
-
# number_to_currency(-0.456789, precision: 0)
-
# # => "$0"
-
# number_to_currency(-1234567890.50, negative_format: "(%u%n)")
-
# # => ($1,234,567,890.50)
-
# number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "")
-
# # => R$1234567890,50
-
# number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u")
-
# # => 1234567890,50 R$
-
# number_to_currency(1234567890.50, strip_insignificant_zeros: true)
-
# # => "$1,234,567,890.5"
-
9
def number_to_currency(number, options = {})
-
delegate_number_helper_method(:number_to_currency, number, options)
-
end
-
-
# Formats a +number+ as a percentage string (e.g., 65%). You can
-
# customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the number
-
# of significant_digits. If +false+, the number of fractional
-
# digits (defaults to +false+).
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +false+).
-
# * <tt>:format</tt> - Specifies the format of the percentage
-
# string The number field is <tt>%n</tt> (defaults to "%n%").
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_to_percentage(100) # => 100.000%
-
# number_to_percentage("98") # => 98.000%
-
# number_to_percentage(100, precision: 0) # => 100%
-
# number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
-
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
-
# number_to_percentage(1000, locale: :fr) # => 1 000,000%
-
# number_to_percentage("98a") # => 98a%
-
# number_to_percentage(100, format: "%n %") # => 100.000 %
-
#
-
# number_to_percentage("98a", raise: true) # => InvalidNumberError
-
9
def number_to_percentage(number, options = {})
-
delegate_number_helper_method(:number_to_percentage, number, options)
-
end
-
-
# Formats a +number+ with grouped thousands using +delimiter+
-
# (e.g., 12,324). You can customize the format in the +options+
-
# hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to ",").
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for
-
# deriving the placement of delimiter. Helpful when using currency formats
-
# like INR.
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_with_delimiter(12345678) # => 12,345,678
-
# number_with_delimiter("123456") # => 123,456
-
# number_with_delimiter(12345678.05) # => 12,345,678.05
-
# number_with_delimiter(12345678, delimiter: ".") # => 12.345.678
-
# number_with_delimiter(12345678, delimiter: ",") # => 12,345,678
-
# number_with_delimiter(12345678.05, separator: " ") # => 12,345,678 05
-
# number_with_delimiter(12345678.05, locale: :fr) # => 12 345 678,05
-
# number_with_delimiter("112a") # => 112a
-
# number_with_delimiter(98765432.98, delimiter: " ", separator: ",")
-
# # => 98 765 432,98
-
#
-
# number_with_delimiter("123456.78",
-
# delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) # => "1,23,456.78"
-
#
-
# number_with_delimiter("112a", raise: true) # => raise InvalidNumberError
-
9
def number_with_delimiter(number, options = {})
-
delegate_number_helper_method(:number_to_delimited, number, options)
-
end
-
-
# Formats a +number+ with the specified level of
-
# <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
-
# +:significant+ is +false+, and 5 if +:significant+ is +true+).
-
# You can customize the format in the +options+ hash.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the number
-
# of significant_digits. If +false+, the number of fractional
-
# digits (defaults to +false+).
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +false+).
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_with_precision(111.2345) # => 111.235
-
# number_with_precision(111.2345, precision: 2) # => 111.23
-
# number_with_precision(13, precision: 5) # => 13.00000
-
# number_with_precision(389.32314, precision: 0) # => 389
-
# number_with_precision(111.2345, significant: true) # => 111
-
# number_with_precision(111.2345, precision: 1, significant: true) # => 100
-
# number_with_precision(13, precision: 5, significant: true) # => 13.000
-
# number_with_precision(111.234, locale: :fr) # => 111,234
-
#
-
# number_with_precision(13, precision: 5, significant: true, strip_insignificant_zeros: true)
-
# # => 13
-
#
-
# number_with_precision(389.32314, precision: 4, significant: true) # => 389.3
-
# number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.')
-
# # => 1.111,23
-
9
def number_with_precision(number, options = {})
-
delegate_number_helper_method(:number_to_rounded, number, options)
-
end
-
-
# Formats the bytes in +number+ into a more understandable
-
# representation (e.g., giving it 1500 yields 1.46 KB). This
-
# method is useful for reporting file sizes to users. You can
-
# customize the format in the +options+ hash.
-
#
-
# See <tt>number_to_human</tt> if you want to pretty-print a
-
# generic number.
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the number
-
# of significant_digits. If +false+, the number of fractional
-
# digits (defaults to +true+)
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +true+)
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_to_human_size(123) # => 123 Bytes
-
# number_to_human_size(1234) # => 1.21 KB
-
# number_to_human_size(12345) # => 12.1 KB
-
# number_to_human_size(1234567) # => 1.18 MB
-
# number_to_human_size(1234567890) # => 1.15 GB
-
# number_to_human_size(1234567890123) # => 1.12 TB
-
# number_to_human_size(1234567890123456) # => 1.1 PB
-
# number_to_human_size(1234567890123456789) # => 1.07 EB
-
# number_to_human_size(1234567, precision: 2) # => 1.2 MB
-
# number_to_human_size(483989, precision: 2) # => 470 KB
-
# number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
-
# number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
-
# number_to_human_size(524288000, precision: 5) # => "500 MB"
-
9
def number_to_human_size(number, options = {})
-
delegate_number_helper_method(:number_to_human_size, number, options)
-
end
-
-
# Pretty prints (formats and approximates) a number in a way it
-
# is more readable by humans (e.g.: 1200000000 becomes "1.2
-
# Billion"). This is useful for numbers that can get very large
-
# (and too hard to read).
-
#
-
# See <tt>number_to_human_size</tt> if you want to print a file
-
# size.
-
#
-
# You can also define your own unit-quantifier names if you want
-
# to use other decimal units (e.g.: 1500 becomes "1.5
-
# kilometers", 0.150 becomes "150 milliliters", etc). You may
-
# define a wide range of unit quantifiers, even fractional ones
-
# (centi, deci, mili, etc).
-
#
-
# ==== Options
-
#
-
# * <tt>:locale</tt> - Sets the locale to be used for formatting
-
# (defaults to current locale).
-
# * <tt>:precision</tt> - Sets the precision of the number
-
# (defaults to 3).
-
# * <tt>:significant</tt> - If +true+, precision will be the number
-
# of significant_digits. If +false+, the number of fractional
-
# digits (defaults to +true+)
-
# * <tt>:separator</tt> - Sets the separator between the
-
# fractional and integer digits (defaults to ".").
-
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
-
# to "").
-
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
-
# insignificant zeros after the decimal separator (defaults to
-
# +true+)
-
# * <tt>:units</tt> - A Hash of unit quantifier names. Or a
-
# string containing an i18n scope where to find this hash. It
-
# might have the following keys:
-
# * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
-
# <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
-
# <tt>:billion</tt>, <tt>:trillion</tt>,
-
# <tt>:quadrillion</tt>
-
# * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
-
# <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
-
# <tt>:pico</tt>, <tt>:femto</tt>
-
# * <tt>:format</tt> - Sets the format of the output string
-
# (defaults to "%n %u"). The field types are:
-
# * %u - The quantifier (ex.: 'thousand')
-
# * %n - The number
-
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
-
# the argument is invalid.
-
#
-
# ==== Examples
-
#
-
# number_to_human(123) # => "123"
-
# number_to_human(1234) # => "1.23 Thousand"
-
# number_to_human(12345) # => "12.3 Thousand"
-
# number_to_human(1234567) # => "1.23 Million"
-
# number_to_human(1234567890) # => "1.23 Billion"
-
# number_to_human(1234567890123) # => "1.23 Trillion"
-
# number_to_human(1234567890123456) # => "1.23 Quadrillion"
-
# number_to_human(1234567890123456789) # => "1230 Quadrillion"
-
# number_to_human(489939, precision: 2) # => "490 Thousand"
-
# number_to_human(489939, precision: 4) # => "489.9 Thousand"
-
# number_to_human(1234567, precision: 4,
-
# significant: false) # => "1.2346 Million"
-
# number_to_human(1234567, precision: 1,
-
# separator: ',',
-
# significant: false) # => "1,2 Million"
-
#
-
# number_to_human(500000000, precision: 5) # => "500 Million"
-
# number_to_human(12345012345, significant: false) # => "12.345 Billion"
-
#
-
# Non-significant zeros after the decimal separator are stripped
-
# out by default (set <tt>:strip_insignificant_zeros</tt> to
-
# +false+ to change that):
-
#
-
# number_to_human(12.00001) # => "12"
-
# number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0"
-
#
-
# ==== Custom Unit Quantifiers
-
#
-
# You can also use your own custom unit quantifiers:
-
# number_to_human(500000, units: {unit: "ml", thousand: "lt"}) # => "500 lt"
-
#
-
# If in your I18n locale you have:
-
# distance:
-
# centi:
-
# one: "centimeter"
-
# other: "centimeters"
-
# unit:
-
# one: "meter"
-
# other: "meters"
-
# thousand:
-
# one: "kilometer"
-
# other: "kilometers"
-
# billion: "gazillion-distance"
-
#
-
# Then you could do:
-
#
-
# number_to_human(543934, units: :distance) # => "544 kilometers"
-
# number_to_human(54393498, units: :distance) # => "54400 kilometers"
-
# number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance"
-
# number_to_human(343, units: :distance, precision: 1) # => "300 meters"
-
# number_to_human(1, units: :distance) # => "1 meter"
-
# number_to_human(0.34, units: :distance) # => "34 centimeters"
-
#
-
9
def number_to_human(number, options = {})
-
delegate_number_helper_method(:number_to_human, number, options)
-
end
-
-
9
private
-
9
def delegate_number_helper_method(method, number, options)
-
return unless number
-
options = escape_unsafe_options(options.symbolize_keys)
-
-
wrap_with_output_safety_handling(number, options.delete(:raise)) {
-
ActiveSupport::NumberHelper.public_send(method, number, options)
-
}
-
end
-
-
9
def escape_unsafe_options(options)
-
options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
-
options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format]
-
options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator]
-
options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter]
-
options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
-
options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units]
-
options
-
end
-
-
9
def escape_units(units)
-
units.transform_values do |v|
-
ERB::Util.html_escape(v)
-
end
-
end
-
-
9
def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
-
valid_float = valid_float?(number)
-
raise InvalidNumberError, number if raise_on_invalid && !valid_float
-
-
formatted_number = yield
-
-
if valid_float || number.html_safe?
-
formatted_number.html_safe
-
else
-
formatted_number
-
end
-
end
-
-
9
def valid_float?(number)
-
!parse_float(number, false).nil?
-
end
-
-
9
def parse_float(number, raise_error)
-
Float(number)
-
rescue ArgumentError, TypeError
-
raise InvalidNumberError, number if raise_error
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "active_support/core_ext/string/output_safety"
-
-
9
module ActionView #:nodoc:
-
# = Action View Raw Output Helper
-
9
module Helpers #:nodoc:
-
9
module OutputSafetyHelper
-
# This method outputs without escaping a string. Since escaping tags is
-
# now default, this can be used when you don't want Rails to automatically
-
# escape tags. This is not recommended if the data is coming from the user's
-
# input.
-
#
-
# For example:
-
#
-
# raw @user.name
-
# # => 'Jimmy <alert>Tables</alert>'
-
9
def raw(stringish)
-
stringish.to_s.html_safe
-
end
-
-
# This method returns an HTML safe string similar to what <tt>Array#join</tt>
-
# would return. The array is flattened, and all items, including
-
# the supplied separator, are HTML escaped unless they are HTML
-
# safe, and the returned string is marked as HTML safe.
-
#
-
# safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
-
# # => "<p>foo</p><br /><p>bar</p>"
-
#
-
# safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />"))
-
# # => "<p>foo</p><br /><p>bar</p>"
-
#
-
9
def safe_join(array, sep = $,)
-
sep = ERB::Util.unwrapped_html_escape(sep)
-
-
array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe
-
end
-
-
# Converts the array to a comma-separated sentence where the last element is
-
# joined by the connector word. This is the html_safe-aware version of
-
# ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
-
#
-
9
def to_sentence(array, options = {})
-
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
-
-
default_connectors = {
-
words_connector: ", ",
-
two_words_connector: " and ",
-
last_word_connector: ", and "
-
}
-
if defined?(I18n)
-
i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
-
default_connectors.merge!(i18n_connectors)
-
end
-
options = default_connectors.merge!(options)
-
-
case array.length
-
when 0
-
"".html_safe
-
when 1
-
ERB::Util.html_escape(array[0])
-
when 2
-
safe_join([array[0], array[1]], options[:two_words_connector])
-
else
-
safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]], nil)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
9
module Helpers #:nodoc:
-
# = Action View Rendering
-
#
-
# Implements methods that allow rendering from a view context.
-
# In order to use this module, all you need is to implement
-
# view_renderer that returns an ActionView::Renderer object.
-
9
module RenderingHelper
-
# Returns the result of a render that's dictated by the options hash. The primary options are:
-
#
-
# * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt>.
-
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
-
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
-
# * <tt>:plain</tt> - Renders the text passed in out. Setting the content
-
# type as <tt>text/plain</tt>.
-
# * <tt>:html</tt> - Renders the HTML safe string passed in out, otherwise
-
# performs HTML escape on the string first. Setting the content type as
-
# <tt>text/html</tt>.
-
# * <tt>:body</tt> - Renders the text passed in, and inherits the content
-
# type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
-
# object.
-
#
-
# If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
-
#
-
# If an object responding to `render_in` is passed, `render_in` is called on the object,
-
# passing in the current view context.
-
#
-
# Otherwise, a partial is rendered using the second parameter as the locals hash.
-
9
def render(options = {}, locals = {}, &block)
-
case options
-
when Hash
-
in_rendering_context(options) do |renderer|
-
if block_given?
-
view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
-
else
-
view_renderer.render(self, options)
-
end
-
end
-
else
-
if options.respond_to?(:render_in)
-
options.render_in(self, &block)
-
else
-
view_renderer.render_partial(self, partial: options, locals: locals, &block)
-
end
-
end
-
end
-
-
# Overwrites _layout_for in the context object so it supports the case a block is
-
# passed to a partial. Returns the contents that are yielded to a layout, given a
-
# name or a block.
-
#
-
# You can think of a layout as a method that is called with a block. If the user calls
-
# <tt>yield :some_name</tt>, the block, by default, returns <tt>content_for(:some_name)</tt>.
-
# If the user calls simply +yield+, the default block returns <tt>content_for(:layout)</tt>.
-
#
-
# The user can override this default by passing a block to the layout:
-
#
-
# # The template
-
# <%= render layout: "my_layout" do %>
-
# Content
-
# <% end %>
-
#
-
# # The layout
-
# <html>
-
# <%= yield %>
-
# </html>
-
#
-
# In this case, instead of the default block, which would return <tt>content_for(:layout)</tt>,
-
# this method returns the block that was passed in to <tt>render :layout</tt>, and the response
-
# would be
-
#
-
# <html>
-
# Content
-
# </html>
-
#
-
# Finally, the block can take block arguments, which can be passed in by +yield+:
-
#
-
# # The template
-
# <%= render layout: "my_layout" do |customer| %>
-
# Hello <%= customer.name %>
-
# <% end %>
-
#
-
# # The layout
-
# <html>
-
# <%= yield Struct.new(:name).new("David") %>
-
# </html>
-
#
-
# In this case, the layout would receive the block passed into <tt>render :layout</tt>,
-
# and the struct specified would be passed into the block as an argument. The result
-
# would be
-
#
-
# <html>
-
# Hello David
-
# </html>
-
#
-
9
def _layout_for(*args, &block)
-
name = args.first
-
-
if block && !name.is_a?(Symbol)
-
capture(*args, &block)
-
else
-
super
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "rails-html-sanitizer"
-
-
9
module ActionView
-
# = Action View Sanitize Helpers
-
9
module Helpers #:nodoc:
-
# The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
-
# These helper methods extend Action View making them callable within your template files.
-
9
module SanitizeHelper
-
9
extend ActiveSupport::Concern
-
# Sanitizes HTML input, stripping all but known-safe tags and attributes.
-
#
-
# It also strips href/src attributes with unsafe protocols like
-
# <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
-
# ASCII, and hex character references to work around these protocol filters.
-
# All special characters will be escaped.
-
#
-
# The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
-
# Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
-
#
-
# Custom sanitization rules can also be provided.
-
#
-
# Please note that sanitizing user-provided text does not guarantee that the
-
# resulting markup is valid or even well-formed.
-
#
-
# ==== Options
-
#
-
# * <tt>:tags</tt> - An array of allowed tags.
-
# * <tt>:attributes</tt> - An array of allowed attributes.
-
# * <tt>:scrubber</tt> - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer]
-
# or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that
-
# defines custom sanitization rules. A custom scrubber takes precedence over
-
# custom tags and attributes.
-
#
-
# ==== Examples
-
#
-
# Normal use:
-
#
-
# <%= sanitize @comment.body %>
-
#
-
# Providing custom lists of permitted tags and attributes:
-
#
-
# <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
-
#
-
# Providing a custom Rails::Html scrubber:
-
#
-
# class CommentScrubber < Rails::Html::PermitScrubber
-
# def initialize
-
# super
-
# self.tags = %w( form script comment blockquote )
-
# self.attributes = %w( style )
-
# end
-
#
-
# def skip_node?(node)
-
# node.text?
-
# end
-
# end
-
#
-
# <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
-
#
-
# See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for
-
# documentation about Rails::Html scrubbers.
-
#
-
# Providing a custom Loofah::Scrubber:
-
#
-
# scrubber = Loofah::Scrubber.new do |node|
-
# node.remove if node.name == 'script'
-
# end
-
#
-
# <%= sanitize @comment.body, scrubber: scrubber %>
-
#
-
# See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more
-
# information about defining custom Loofah::Scrubber objects.
-
#
-
# To set the default allowed tags or attributes across your application:
-
#
-
# # In config/application.rb
-
# config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
-
# config.action_view.sanitized_allowed_attributes = ['href', 'title']
-
9
def sanitize(html, options = {})
-
self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
-
end
-
-
# Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
-
9
def sanitize_css(style)
-
self.class.safe_list_sanitizer.sanitize_css(style)
-
end
-
-
# Strips all HTML tags from +html+, including comments and special characters.
-
#
-
# strip_tags("Strip <i>these</i> tags!")
-
# # => Strip these tags!
-
#
-
# strip_tags("<b>Bold</b> no more! <a href='more.html'>See more here</a>...")
-
# # => Bold no more! See more here...
-
#
-
# strip_tags("<div id='top-bar'>Welcome to my website!</div>")
-
# # => Welcome to my website!
-
#
-
# strip_tags("> A quote from Smith & Wesson")
-
# # => > A quote from Smith & Wesson
-
9
def strip_tags(html)
-
self.class.full_sanitizer.sanitize(html)
-
end
-
-
# Strips all link tags from +html+ leaving just the link text.
-
#
-
# strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
-
# # => Ruby on Rails
-
#
-
# strip_links('Please e-mail me at <a href="mailto:me@email.com">me@email.com</a>.')
-
# # => Please e-mail me at me@email.com.
-
#
-
# strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.')
-
# # => Blog: Visit.
-
#
-
# strip_links('<<a href="https://example.org">malformed & link</a>')
-
# # => <malformed & link
-
9
def strip_links(html)
-
self.class.link_sanitizer.sanitize(html)
-
end
-
-
9
module ClassMethods #:nodoc:
-
9
attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
-
-
9
def sanitizer_vendor
-
Rails::Html::Sanitizer
-
end
-
-
9
def sanitized_allowed_tags
-
safe_list_sanitizer.allowed_tags
-
end
-
-
9
def sanitized_allowed_attributes
-
safe_list_sanitizer.allowed_attributes
-
end
-
-
# Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
-
# any object that responds to +sanitize+.
-
#
-
# class Application < Rails::Application
-
# config.action_view.full_sanitizer = MySpecialSanitizer.new
-
# end
-
9
def full_sanitizer
-
@full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
-
end
-
-
# Gets the Rails::Html::LinkSanitizer instance used by +strip_links+.
-
# Replace with any object that responds to +sanitize+.
-
#
-
# class Application < Rails::Application
-
# config.action_view.link_sanitizer = MySpecialSanitizer.new
-
# end
-
9
def link_sanitizer
-
@link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
-
end
-
-
# Gets the Rails::Html::SafeListSanitizer instance used by sanitize and +sanitize_css+.
-
# Replace with any object that responds to +sanitize+.
-
#
-
# class Application < Rails::Application
-
# config.action_view.safe_list_sanitizer = MySpecialSanitizer.new
-
# end
-
9
def safe_list_sanitizer
-
@safe_list_sanitizer ||= sanitizer_vendor.safe_list_sanitizer.new
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "active_support/core_ext/string/output_safety"
-
9
require "set"
-
-
9
module ActionView
-
# = Action View Tag Helpers
-
9
module Helpers #:nodoc:
-
# Provides methods to generate HTML tags programmatically both as a modern
-
# HTML5 compliant builder style and legacy XHTML compliant tags.
-
9
module TagHelper
-
9
extend ActiveSupport::Concern
-
9
include CaptureHelper
-
9
include OutputSafetyHelper
-
-
9
BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus
-
autoplay checked compact controls declare default
-
defaultchecked defaultmuted defaultselected defer
-
disabled enabled formnovalidate hidden indeterminate
-
inert ismap itemscope loop multiple muted nohref
-
nomodule noresize noshade novalidate nowrap open
-
pauseonexit playsinline readonly required reversed
-
scoped seamless selected sortable truespeed
-
typemustmatch visible).to_set
-
-
9
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
-
9
BOOLEAN_ATTRIBUTES.freeze
-
-
9
TAG_PREFIXES = ["aria", "data", :aria, :data].to_set.freeze
-
-
9
TAG_TYPES = {}
-
9
TAG_TYPES.merge! BOOLEAN_ATTRIBUTES.index_with(:boolean)
-
9
TAG_TYPES.merge! TAG_PREFIXES.index_with(:prefix)
-
9
TAG_TYPES.freeze
-
-
9
PRE_CONTENT_STRINGS = Hash.new { "" }
-
9
PRE_CONTENT_STRINGS[:textarea] = "\n"
-
9
PRE_CONTENT_STRINGS["textarea"] = "\n"
-
-
9
class TagBuilder #:nodoc:
-
9
include CaptureHelper
-
9
include OutputSafetyHelper
-
-
9
VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set
-
-
9
def initialize(view_context)
-
@view_context = view_context
-
end
-
-
9
def tag_string(name, content = nil, escape_attributes: true, **options, &block)
-
content = @view_context.capture(self, &block) if block_given?
-
if VOID_ELEMENTS.include?(name) && content.nil?
-
"<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
-
else
-
content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
-
end
-
end
-
-
9
def content_tag_string(name, content, options, escape = true)
-
tag_options = tag_options(options, escape) if options
-
content = ERB::Util.unwrapped_html_escape(content) if escape
-
"<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
-
end
-
-
9
def tag_options(options, escape = true)
-
return if options.blank?
-
output = +""
-
sep = " "
-
options.each_pair do |key, value|
-
type = TAG_TYPES[key]
-
if type == :prefix && value.is_a?(Hash)
-
value.each_pair do |k, v|
-
next if v.nil?
-
output << sep
-
output << prefix_tag_option(key, k, v, escape)
-
end
-
elsif type == :boolean
-
if value
-
output << sep
-
output << boolean_tag_option(key)
-
end
-
elsif !value.nil?
-
output << sep
-
output << tag_option(key, value, escape)
-
end
-
end
-
output unless output.empty?
-
end
-
-
9
def boolean_tag_option(key)
-
%(#{key}="#{key}")
-
end
-
-
9
def tag_option(key, value, escape)
-
case value
-
when Array, Hash
-
value = TagHelper.build_tag_values(value) if key.to_s == "class"
-
value = escape ? safe_join(value, " ") : value.join(" ")
-
else
-
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
-
end
-
value = value.gsub('"', """) if value.include?('"')
-
%(#{key}="#{value}")
-
end
-
-
9
private
-
9
def prefix_tag_option(prefix, key, value, escape)
-
key = "#{prefix}-#{key.to_s.dasherize}"
-
unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
-
value = value.to_json
-
end
-
tag_option(key, value, escape)
-
end
-
-
9
def respond_to_missing?(*args)
-
true
-
end
-
-
9
def method_missing(called, *args, **options, &block)
-
tag_string(called, *args, **options, &block)
-
end
-
end
-
-
# Returns an HTML tag.
-
#
-
# === Building HTML tags
-
#
-
# Builds HTML5 compliant tags with a tag proxy. Every tag can be built with:
-
#
-
# tag.<tag name>(optional content, options)
-
#
-
# where tag name can be e.g. br, div, section, article, or any tag really.
-
#
-
# ==== Passing content
-
#
-
# Tags can pass content to embed within it:
-
#
-
# tag.h1 'All titles fit to print' # => <h1>All titles fit to print</h1>
-
#
-
# tag.div tag.p('Hello world!') # => <div><p>Hello world!</p></div>
-
#
-
# Content can also be captured with a block, which is useful in templates:
-
#
-
# <%= tag.p do %>
-
# The next great American novel starts here.
-
# <% end %>
-
# # => <p>The next great American novel starts here.</p>
-
#
-
# ==== Options
-
#
-
# Use symbol keyed options to add attributes to the generated tag.
-
#
-
# tag.section class: %w( kitties puppies )
-
# # => <section class="kitties puppies"></section>
-
#
-
# tag.section id: dom_id(@post)
-
# # => <section id="<generated dom id>"></section>
-
#
-
# Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+.
-
#
-
# tag.input type: 'text', disabled: true
-
# # => <input type="text" disabled="disabled">
-
#
-
# HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
-
# pointing to a hash of sub-attributes.
-
#
-
# To play nicely with JavaScript conventions, sub-attributes are dasherized.
-
#
-
# tag.article data: { user_id: 123 }
-
# # => <article data-user-id="123"></article>
-
#
-
# Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>.
-
#
-
# Data attribute values are encoded to JSON, with the exception of strings, symbols and
-
# BigDecimals.
-
# This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
-
# from 1.4.3.
-
#
-
# tag.div data: { city_state: %w( Chicago IL ) }
-
# # => <div data-city-state="["Chicago","IL"]"></div>
-
#
-
# The generated attributes are escaped by default. This can be disabled using
-
# +escape_attributes+.
-
#
-
# tag.img src: 'open & shut.png'
-
# # => <img src="open & shut.png">
-
#
-
# tag.img src: 'open & shut.png', escape_attributes: false
-
# # => <img src="open & shut.png">
-
#
-
# The tag builder respects
-
# {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements]
-
# if no content is passed, and omits closing tags for those elements.
-
#
-
# # A standard element:
-
# tag.div # => <div></div>
-
#
-
# # A void element:
-
# tag.br # => <br>
-
#
-
# === Legacy syntax
-
#
-
# The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
-
#
-
# tag(name, options = nil, open = false, escape = true)
-
#
-
# It returns an empty HTML tag of type +name+ which by default is XHTML
-
# compliant. Set +open+ to true to create an open tag compatible
-
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
-
# hash to +options+. Set +escape+ to false to disable attribute value
-
# escaping.
-
#
-
# ==== Options
-
#
-
# You can use symbols or strings for the attribute names.
-
#
-
# Use +true+ with boolean attributes that can render with no value, like
-
# +disabled+ and +readonly+.
-
#
-
# HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
-
# pointing to a hash of sub-attributes.
-
#
-
# ==== Examples
-
#
-
# tag("br")
-
# # => <br />
-
#
-
# tag("br", nil, true)
-
# # => <br>
-
#
-
# tag("input", type: 'text', disabled: true)
-
# # => <input type="text" disabled="disabled" />
-
#
-
# tag("input", type: 'text', class: ["strong", "highlight"])
-
# # => <input class="strong highlight" type="text" />
-
#
-
# tag("img", src: "open & shut.png")
-
# # => <img src="open & shut.png" />
-
#
-
# tag("img", { src: "open & shut.png" }, false, false)
-
# # => <img src="open & shut.png" />
-
#
-
# tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
-
# # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" />
-
#
-
# tag("div", class: { highlight: current_user.admin? })
-
# # => <div class="highlight" />
-
9
def tag(name = nil, options = nil, open = false, escape = true)
-
if name.nil?
-
tag_builder
-
else
-
"<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
-
end
-
end
-
-
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
-
# HTML attributes by passing an attributes hash to +options+.
-
# Instead of passing the content as an argument, you can also use a block
-
# in which case, you pass your +options+ as the second parameter.
-
# Set escape to false to disable attribute value escaping.
-
# Note: this is legacy syntax, see +tag+ method description for details.
-
#
-
# ==== Options
-
# The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
-
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
-
# symbols or strings for the attribute names.
-
#
-
# ==== Examples
-
# content_tag(:p, "Hello world!")
-
# # => <p>Hello world!</p>
-
# content_tag(:div, content_tag(:p, "Hello world!"), class: "strong")
-
# # => <div class="strong"><p>Hello world!</p></div>
-
# content_tag(:div, "Hello world!", class: ["strong", "highlight"])
-
# # => <div class="strong highlight">Hello world!</div>
-
# content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }])
-
# # => <div class="strong highlight">Hello world!</div>
-
# content_tag("select", options, multiple: true)
-
# # => <select multiple="multiple">...options...</select>
-
#
-
# <%= content_tag :div, class: "strong" do -%>
-
# Hello world!
-
# <% end -%>
-
# # => <div class="strong">Hello world!</div>
-
9
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
-
if block_given?
-
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
-
tag_builder.content_tag_string(name, capture(&block), options, escape)
-
else
-
tag_builder.content_tag_string(name, content_or_options_with_block, options, escape)
-
end
-
end
-
-
# Returns a string of class names built from +args+.
-
#
-
# ==== Examples
-
# class_names("foo", "bar")
-
# # => "foo bar"
-
# class_names({ foo: true, bar: false })
-
# # => "foo"
-
# class_names(nil, false, 123, "", "foo", { bar: true })
-
# # => "123 foo bar"
-
9
def class_names(*args)
-
safe_join(build_tag_values(*args), " ")
-
end
-
-
# Returns a CDATA section with the given +content+. CDATA sections
-
# are used to escape blocks of text containing characters which would
-
# otherwise be recognized as markup. CDATA sections begin with the string
-
# <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
-
#
-
# cdata_section("<hello world>")
-
# # => <![CDATA[<hello world>]]>
-
#
-
# cdata_section(File.read("hello_world.txt"))
-
# # => <![CDATA[<hello from a text file]]>
-
#
-
# cdata_section("hello]]>world")
-
# # => <![CDATA[hello]]]]><![CDATA[>world]]>
-
9
def cdata_section(content)
-
splitted = content.to_s.gsub(/\]\]\>/, "]]]]><![CDATA[>")
-
"<![CDATA[#{splitted}]]>".html_safe
-
end
-
-
# Returns an escaped version of +html+ without affecting existing escaped entities.
-
#
-
# escape_once("1 < 2 & 3")
-
# # => "1 < 2 & 3"
-
#
-
# escape_once("<< Accept & Checkout")
-
# # => "<< Accept & Checkout"
-
9
def escape_once(html)
-
ERB::Util.html_escape_once(html)
-
end
-
-
9
private
-
9
def build_tag_values(*args)
-
tag_values = []
-
-
args.each do |tag_value|
-
case tag_value
-
when Hash
-
tag_value.each do |key, val|
-
tag_values << key.to_s if val && key.present?
-
end
-
when Array
-
tag_values.concat build_tag_values(*tag_value)
-
else
-
tag_values << tag_value.to_s if tag_value.present?
-
end
-
end
-
-
tag_values
-
end
-
9
module_function :build_tag_values
-
-
9
def tag_builder
-
@tag_builder ||= TagBuilder.new(self)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActionView
-
3
module Helpers #:nodoc:
-
3
module Tags #:nodoc:
-
3
extend ActiveSupport::Autoload
-
-
3
eager_autoload do
-
3
autoload :Base
-
3
autoload :Translator
-
3
autoload :CheckBox
-
3
autoload :CollectionCheckBoxes
-
3
autoload :CollectionRadioButtons
-
3
autoload :CollectionSelect
-
3
autoload :ColorField
-
3
autoload :DateField
-
3
autoload :DateSelect
-
3
autoload :DatetimeField
-
3
autoload :DatetimeLocalField
-
3
autoload :DatetimeSelect
-
3
autoload :EmailField
-
3
autoload :FileField
-
3
autoload :GroupedCollectionSelect
-
3
autoload :HiddenField
-
3
autoload :Label
-
3
autoload :MonthField
-
3
autoload :NumberField
-
3
autoload :PasswordField
-
3
autoload :RadioButton
-
3
autoload :RangeField
-
3
autoload :SearchField
-
3
autoload :Select
-
3
autoload :TelField
-
3
autoload :TextArea
-
3
autoload :TextField
-
3
autoload :TimeField
-
3
autoload :TimeSelect
-
3
autoload :TimeZoneSelect
-
3
autoload :UrlField
-
3
autoload :WeekField
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
module ActionView
-
3
module Helpers
-
3
module Tags # :nodoc:
-
3
class Base # :nodoc:
-
3
include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
-
3
include FormOptionsHelper
-
-
3
attr_reader :object
-
-
3
def initialize(object_name, method_name, template_object, options = {})
-
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
-
@template_object = template_object
-
-
@object_name.sub!(/\[\]$/, "") || @object_name.sub!(/\[\]\]$/, "]")
-
@object = retrieve_object(options.delete(:object))
-
@skip_default_ids = options.delete(:skip_default_ids)
-
@allow_method_names_outside_object = options.delete(:allow_method_names_outside_object)
-
@options = options
-
-
if Regexp.last_match
-
@generate_indexed_names = true
-
@auto_index = retrieve_autoindex(Regexp.last_match.pre_match)
-
else
-
@generate_indexed_names = false
-
@auto_index = nil
-
end
-
end
-
-
# This is what child classes implement.
-
3
def render
-
raise NotImplementedError, "Subclasses must implement a render method"
-
end
-
-
3
private
-
3
def value
-
if @allow_method_names_outside_object
-
object.public_send @method_name if object && object.respond_to?(@method_name)
-
else
-
object.public_send @method_name if object
-
end
-
end
-
-
3
def value_before_type_cast
-
unless object.nil?
-
method_before_type_cast = @method_name + "_before_type_cast"
-
-
if value_came_from_user? && object.respond_to?(method_before_type_cast)
-
object.public_send(method_before_type_cast)
-
else
-
value
-
end
-
end
-
end
-
-
3
def value_came_from_user?
-
method_name = "#{@method_name}_came_from_user?"
-
!object.respond_to?(method_name) || object.public_send(method_name)
-
end
-
-
3
def retrieve_object(object)
-
if object
-
object
-
elsif @template_object.instance_variable_defined?("@#{@object_name}")
-
@template_object.instance_variable_get("@#{@object_name}")
-
end
-
rescue NameError
-
# As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
-
nil
-
end
-
-
3
def retrieve_autoindex(pre_match)
-
object = self.object || @template_object.instance_variable_get("@#{pre_match}")
-
if object && object.respond_to?(:to_param)
-
object.to_param
-
else
-
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
-
end
-
end
-
-
3
def add_default_name_and_id_for_value(tag_value, options)
-
if tag_value.nil?
-
add_default_name_and_id(options)
-
else
-
specified_id = options["id"]
-
add_default_name_and_id(options)
-
-
if specified_id.blank? && options["id"].present?
-
options["id"] += "_#{sanitized_value(tag_value)}"
-
end
-
end
-
end
-
-
3
def add_default_name_and_id(options)
-
index = name_and_id_index(options)
-
options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
-
-
if generate_ids?
-
options["id"] = options.fetch("id") { tag_id(index) }
-
if namespace = options.delete("namespace")
-
options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
-
end
-
end
-
end
-
-
3
def tag_name(multiple = false, index = nil)
-
# a little duplication to construct fewer strings
-
case
-
when @object_name.empty?
-
"#{sanitized_method_name}#{multiple ? "[]" : ""}"
-
when index
-
"#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}"
-
else
-
"#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}"
-
end
-
end
-
-
3
def tag_id(index = nil)
-
# a little duplication to construct fewer strings
-
case
-
when @object_name.empty?
-
sanitized_method_name.dup
-
when index
-
"#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
-
else
-
"#{sanitized_object_name}_#{sanitized_method_name}"
-
end
-
end
-
-
3
def sanitized_object_name
-
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").delete_suffix("_")
-
end
-
-
3
def sanitized_method_name
-
@sanitized_method_name ||= @method_name.delete_suffix("?")
-
end
-
-
3
def sanitized_value(value)
-
value.to_s.gsub(/[\s\.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
-
end
-
-
3
def select_content_tag(option_tags, options, html_options)
-
html_options = html_options.stringify_keys
-
add_default_name_and_id(html_options)
-
-
if placeholder_required?(html_options)
-
raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
-
options[:include_blank] ||= true unless options[:prompt]
-
end
-
-
value = options.fetch(:selected) { value() }
-
select = content_tag("select", add_options(option_tags, options, value), html_options)
-
-
if html_options["multiple"] && options.fetch(:include_hidden, true)
-
tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "") + select
-
else
-
select
-
end
-
end
-
-
3
def placeholder_required?(html_options)
-
# See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
-
html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
-
end
-
-
3
def add_options(option_tags, options, value = nil)
-
if options[:include_blank]
-
content = (options[:include_blank] if options[:include_blank].is_a?(String))
-
label = (" " unless content)
-
option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
-
end
-
-
if value.blank? && options[:prompt]
-
tag_options = { value: "" }.tap do |prompt_opts|
-
prompt_opts[:disabled] = true if options[:disabled] == ""
-
prompt_opts[:selected] = true if options[:selected] == ""
-
end
-
option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
-
end
-
-
option_tags
-
end
-
-
3
def name_and_id_index(options)
-
if options.key?("index")
-
options.delete("index") || ""
-
elsif @generate_indexed_names
-
@auto_index || ""
-
end
-
end
-
-
3
def generate_ids?
-
!@skip_default_ids
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "action_view/helpers/tags/checkable"
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class CheckBox < Base #:nodoc:
-
include Checkable
-
-
def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options)
-
@checked_value = checked_value
-
@unchecked_value = unchecked_value
-
super(object_name, method_name, template_object, options)
-
end
-
-
def render
-
options = @options.stringify_keys
-
options["type"] = "checkbox"
-
options["value"] = @checked_value
-
options["checked"] = "checked" if input_checked?(options)
-
-
if options["multiple"]
-
add_default_name_and_id_for_value(@checked_value, options)
-
options.delete("multiple")
-
else
-
add_default_name_and_id(options)
-
end
-
-
include_hidden = options.delete("include_hidden") { true }
-
checkbox = tag("input", options)
-
-
if include_hidden
-
hidden = hidden_field_for_checkbox(options)
-
hidden + checkbox
-
else
-
checkbox
-
end
-
end
-
-
private
-
def checked?(value)
-
case value
-
when TrueClass, FalseClass
-
value == !!@checked_value
-
when NilClass
-
false
-
when String
-
value == @checked_value
-
else
-
if value.respond_to?(:include?)
-
value.include?(@checked_value)
-
else
-
value.to_i == @checked_value.to_i
-
end
-
end
-
end
-
-
def hidden_field_for_checkbox(options)
-
@unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
module Checkable # :nodoc:
-
def input_checked?(options)
-
if options.has_key?("checked")
-
checked = options.delete "checked"
-
checked == true || checked == "checked"
-
else
-
checked?(value)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "action_view/helpers/tags/collection_helpers"
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class CollectionCheckBoxes < Base # :nodoc:
-
include CollectionHelpers
-
-
class CheckBoxBuilder < Builder # :nodoc:
-
def check_box(extra_html_options = {})
-
html_options = extra_html_options.merge(@input_html_options)
-
html_options[:multiple] = true
-
html_options[:skip_default_ids] = false
-
@template_object.check_box(@object_name, @method_name, html_options, @value, nil)
-
end
-
end
-
-
def render(&block)
-
render_collection_for(CheckBoxBuilder, &block)
-
end
-
-
private
-
def render_component(builder)
-
builder.check_box + builder.label
-
end
-
-
def hidden_field_name
-
"#{super}[]"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
module CollectionHelpers # :nodoc:
-
class Builder # :nodoc:
-
attr_reader :object, :text, :value
-
-
def initialize(template_object, object_name, method_name, object,
-
sanitized_attribute_name, text, value, input_html_options)
-
@template_object = template_object
-
@object_name = object_name
-
@method_name = method_name
-
@object = object
-
@sanitized_attribute_name = sanitized_attribute_name
-
@text = text
-
@value = value
-
@input_html_options = input_html_options
-
end
-
-
def label(label_html_options = {}, &block)
-
html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options)
-
html_options[:for] ||= @input_html_options[:id] if @input_html_options[:id]
-
-
@template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block)
-
end
-
end
-
-
def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
-
@collection = collection
-
@value_method = value_method
-
@text_method = text_method
-
@html_options = html_options
-
-
super(object_name, method_name, template_object, options)
-
end
-
-
private
-
def instantiate_builder(builder_class, item, value, text, html_options)
-
builder_class.new(@template_object, @object_name, @method_name, item,
-
sanitize_attribute_name(value), text, value, html_options)
-
end
-
-
# Generate default options for collection helpers, such as :checked and
-
# :disabled.
-
def default_html_options_for_collection(item, value)
-
html_options = @html_options.dup
-
-
[:checked, :selected, :disabled, :readonly].each do |option|
-
current_value = @options[option]
-
next if current_value.nil?
-
-
accept = if current_value.respond_to?(:call)
-
current_value.call(item)
-
else
-
Array(current_value).map(&:to_s).include?(value.to_s)
-
end
-
-
if accept
-
html_options[option] = true
-
elsif option == :checked
-
html_options[option] = false
-
end
-
end
-
-
html_options[:object] = @object
-
html_options
-
end
-
-
def sanitize_attribute_name(value)
-
"#{sanitized_method_name}_#{sanitized_value(value)}"
-
end
-
-
def render_collection
-
@collection.map do |item|
-
value = value_for_collection(item, @value_method)
-
text = value_for_collection(item, @text_method)
-
default_html_options = default_html_options_for_collection(item, value)
-
additional_html_options = option_html_attributes(item)
-
-
yield item, value, text, default_html_options.merge(additional_html_options)
-
end.join.html_safe
-
end
-
-
def render_collection_for(builder_class, &block)
-
options = @options.stringify_keys
-
rendered_collection = render_collection do |item, value, text, default_html_options|
-
builder = instantiate_builder(builder_class, item, value, text, default_html_options)
-
-
if block_given?
-
@template_object.capture(builder, &block)
-
else
-
render_component(builder)
-
end
-
end
-
-
# Prepend a hidden field to make sure something will be sent back to the
-
# server if all radio buttons are unchecked.
-
if options.fetch("include_hidden", true)
-
hidden_field + rendered_collection
-
else
-
rendered_collection
-
end
-
end
-
-
def hidden_field
-
hidden_name = @html_options[:name] || hidden_field_name
-
@template_object.hidden_field_tag(hidden_name, "", id: nil)
-
end
-
-
def hidden_field_name
-
"#{tag_name(false, @options[:index])}"
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "action_view/helpers/tags/collection_helpers"
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class CollectionRadioButtons < Base # :nodoc:
-
include CollectionHelpers
-
-
class RadioButtonBuilder < Builder # :nodoc:
-
def radio_button(extra_html_options = {})
-
html_options = extra_html_options.merge(@input_html_options)
-
html_options[:skip_default_ids] = false
-
@template_object.radio_button(@object_name, @method_name, @value, html_options)
-
end
-
end
-
-
def render(&block)
-
render_collection_for(RadioButtonBuilder, &block)
-
end
-
-
private
-
def render_component(builder)
-
builder.radio_button + builder.label
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class CollectionSelect < Base #:nodoc:
-
def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
-
@collection = collection
-
@value_method = value_method
-
@text_method = text_method
-
@html_options = html_options
-
-
super(object_name, method_name, template_object, options)
-
end
-
-
def render
-
option_tags_options = {
-
selected: @options.fetch(:selected) { value },
-
disabled: @options[:disabled]
-
}
-
-
select_content_tag(
-
options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options),
-
@options, @html_options
-
)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class ColorField < TextField # :nodoc:
-
def render
-
options = @options.stringify_keys
-
options["value"] ||= validate_color_string(value)
-
@options = options
-
super
-
end
-
-
private
-
def validate_color_string(string)
-
regex = /#[0-9a-fA-F]{6}/
-
if regex.match?(string)
-
string.downcase
-
else
-
"#000000"
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class DateField < DatetimeField # :nodoc:
-
private
-
def format_date(value)
-
value&.strftime("%Y-%m-%d")
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/time/calculations"
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class DateSelect < Base # :nodoc:
-
def initialize(object_name, method_name, template_object, options, html_options)
-
@html_options = html_options
-
-
super(object_name, method_name, template_object, options)
-
end
-
-
def render
-
error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe)
-
end
-
-
class << self
-
def select_type
-
@select_type ||= name.split("::").last.sub("Select", "").downcase
-
end
-
end
-
-
private
-
def select_type
-
self.class.select_type
-
end
-
-
def datetime_selector(options, html_options)
-
datetime = options.fetch(:selected) { value || default_datetime(options) }
-
@auto_index ||= nil
-
-
options = options.dup
-
options[:field_name] = @method_name
-
options[:include_position] = true
-
options[:prefix] ||= @object_name
-
options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
-
-
DateTimeSelector.new(datetime, options, html_options)
-
end
-
-
def default_datetime(options)
-
return if options[:include_blank] || options[:prompt]
-
-
case options[:default]
-
when nil
-
Time.current
-
when Date, Time
-
options[:default]
-
else
-
default = options[:default].dup
-
-
# Rename :minute and :second to :min and :sec
-
default[:min] ||= default[:minute]
-
default[:sec] ||= default[:second]
-
-
time = Time.current
-
-
[:year, :month, :day, :hour, :min, :sec].each do |key|
-
default[key] ||= time.send(key)
-
end
-
-
Time.utc(
-
default[:year], default[:month], default[:day],
-
default[:hour], default[:min], default[:sec]
-
)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class DatetimeField < TextField # :nodoc:
-
def render
-
options = @options.stringify_keys
-
options["value"] ||= format_date(value)
-
options["min"] = format_date(datetime_value(options["min"]))
-
options["max"] = format_date(datetime_value(options["max"]))
-
@options = options
-
super
-
end
-
-
private
-
def format_date(value)
-
raise NotImplementedError
-
end
-
-
def datetime_value(value)
-
if value.is_a? String
-
DateTime.parse(value) rescue nil
-
else
-
value
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class DatetimeLocalField < DatetimeField # :nodoc:
-
class << self
-
def field_type
-
@field_type ||= "datetime-local"
-
end
-
end
-
-
private
-
def format_date(value)
-
value&.strftime("%Y-%m-%dT%T")
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class DatetimeSelect < DateSelect # :nodoc:
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class EmailField < TextField # :nodoc:
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class FileField < TextField # :nodoc:
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class GroupedCollectionSelect < Base # :nodoc:
-
def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
-
@collection = collection
-
@group_method = group_method
-
@group_label_method = group_label_method
-
@option_key_method = option_key_method
-
@option_value_method = option_value_method
-
@html_options = html_options
-
-
super(object_name, method_name, template_object, options)
-
end
-
-
def render
-
option_tags_options = {
-
selected: @options.fetch(:selected) { value },
-
disabled: @options[:disabled]
-
}
-
-
select_content_tag(
-
option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options
-
)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class HiddenField < TextField # :nodoc:
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class Label < Base # :nodoc:
-
class LabelBuilder # :nodoc:
-
attr_reader :object
-
-
def initialize(template_object, object_name, method_name, object, tag_value)
-
@template_object = template_object
-
@object_name = object_name
-
@method_name = method_name
-
@object = object
-
@tag_value = tag_value
-
end
-
-
def translation
-
method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name
-
-
content ||= Translator
-
.new(object, @object_name, method_and_value, scope: "helpers.label")
-
.translate
-
content ||= @method_name.humanize
-
-
content
-
end
-
end
-
-
def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
-
options ||= {}
-
-
content_is_options = content_or_options.is_a?(Hash)
-
if content_is_options
-
options.merge! content_or_options
-
@content = nil
-
else
-
@content = content_or_options
-
end
-
-
super(object_name, method_name, template_object, options)
-
end
-
-
def render(&block)
-
options = @options.stringify_keys
-
tag_value = options.delete("value")
-
name_and_id = options.dup
-
-
if name_and_id["for"]
-
name_and_id["id"] = name_and_id["for"]
-
else
-
name_and_id.delete("id")
-
end
-
-
add_default_name_and_id_for_value(tag_value, name_and_id)
-
options.delete("index")
-
options.delete("namespace")
-
options["for"] = name_and_id["id"] unless options.key?("for")
-
-
builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value)
-
-
content = if block_given?
-
@template_object.capture(builder, &block)
-
elsif @content.present?
-
@content.to_s
-
else
-
render_component(builder)
-
end
-
-
label_tag(name_and_id["id"], content, options)
-
end
-
-
private
-
def render_component(builder)
-
builder.translation
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class MonthField < DatetimeField # :nodoc:
-
private
-
def format_date(value)
-
value&.strftime("%Y-%m")
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class NumberField < TextField # :nodoc:
-
def render
-
options = @options.stringify_keys
-
-
if range = options.delete("in") || options.delete("within")
-
options.update("min" => range.min, "max" => range.max)
-
end
-
-
@options = options
-
super
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class PasswordField < TextField # :nodoc:
-
def render
-
@options = { value: nil }.merge!(@options)
-
super
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
module Placeholderable # :nodoc:
-
def initialize(*)
-
super
-
-
if tag_value = @options[:placeholder]
-
placeholder = tag_value if tag_value.is_a?(String)
-
method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}"
-
-
placeholder ||= Tags::Translator
-
.new(object, @object_name, method_and_value, scope: "helpers.placeholder")
-
.translate
-
placeholder ||= @method_name.humanize
-
@options[:placeholder] = placeholder
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "action_view/helpers/tags/checkable"
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class RadioButton < Base # :nodoc:
-
include Checkable
-
-
def initialize(object_name, method_name, template_object, tag_value, options)
-
@tag_value = tag_value
-
super(object_name, method_name, template_object, options)
-
end
-
-
def render
-
options = @options.stringify_keys
-
options["type"] = "radio"
-
options["value"] = @tag_value
-
options["checked"] = "checked" if input_checked?(options)
-
add_default_name_and_id_for_value(@tag_value, options)
-
tag("input", options)
-
end
-
-
private
-
def checked?(value)
-
value.to_s == @tag_value.to_s
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class RangeField < NumberField # :nodoc:
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class SearchField < TextField # :nodoc:
-
def render
-
options = @options.stringify_keys
-
-
if options["autosave"]
-
if options["autosave"] == true
-
options["autosave"] = request.host.split(".").reverse.join(".")
-
end
-
options["results"] ||= 10
-
end
-
-
if options["onsearch"]
-
options["incremental"] = true unless options.has_key?("incremental")
-
end
-
-
@options = options
-
super
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class Select < Base # :nodoc:
-
def initialize(object_name, method_name, template_object, choices, options, html_options)
-
@choices = block_given? ? template_object.capture { yield || "" } : choices
-
@choices = @choices.to_a if @choices.is_a?(Range)
-
-
@html_options = html_options
-
-
super(object_name, method_name, template_object, options)
-
end
-
-
def render
-
option_tags_options = {
-
selected: @options.fetch(:selected) { value.to_s },
-
disabled: @options[:disabled]
-
}
-
-
option_tags = if grouped_choices?
-
grouped_options_for_select(@choices, option_tags_options)
-
else
-
options_for_select(@choices, option_tags_options)
-
end
-
-
select_content_tag(option_tags, @options, @html_options)
-
end
-
-
private
-
# Grouped choices look like this:
-
#
-
# [nil, []]
-
# { nil => [] }
-
def grouped_choices?
-
!@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class TelField < TextField # :nodoc:
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "action_view/helpers/tags/placeholderable"
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class TextArea < Base # :nodoc:
-
include Placeholderable
-
-
def render
-
options = @options.stringify_keys
-
add_default_name_and_id(options)
-
-
if size = options.delete("size")
-
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
-
end
-
-
content_tag("textarea", options.delete("value") { value_before_type_cast }, options)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "action_view/helpers/tags/placeholderable"
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class TextField < Base # :nodoc:
-
include Placeholderable
-
-
def render
-
options = @options.stringify_keys
-
options["size"] = options["maxlength"] unless options.key?("size")
-
options["type"] ||= field_type
-
options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
-
add_default_name_and_id(options)
-
tag("input", options)
-
end
-
-
class << self
-
def field_type
-
@field_type ||= name.split("::").last.sub("Field", "").downcase
-
end
-
end
-
-
private
-
def field_type
-
self.class.field_type
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class TimeField < DatetimeField # :nodoc:
-
private
-
def format_date(value)
-
value&.strftime("%T.%L")
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class TimeSelect < DateSelect # :nodoc:
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class TimeZoneSelect < Base # :nodoc:
-
def initialize(object_name, method_name, template_object, priority_zones, options, html_options)
-
@priority_zones = priority_zones
-
@html_options = html_options
-
-
super(object_name, method_name, template_object, options)
-
end
-
-
def render
-
select_content_tag(
-
time_zone_options_for_select(value || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
-
)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class Translator # :nodoc:
-
def initialize(object, object_name, method_and_value, scope:)
-
@object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
-
@method_and_value = method_and_value
-
@scope = scope
-
@model = object.respond_to?(:to_model) ? object.to_model : nil
-
end
-
-
def translate
-
translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence
-
translated_attribute || human_attribute_name
-
end
-
-
private
-
attr_reader :object_name, :method_and_value, :scope, :model
-
-
def i18n_default
-
if model
-
key = model.model_name.i18n_key
-
["#{key}.#{method_and_value}".to_sym, ""]
-
else
-
""
-
end
-
end
-
-
def human_attribute_name
-
if model && model.class.respond_to?(:human_attribute_name)
-
model.class.human_attribute_name(method_and_value)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class UrlField < TextField # :nodoc:
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
module Helpers
-
module Tags # :nodoc:
-
class WeekField < DatetimeField # :nodoc:
-
private
-
def format_date(value)
-
value&.strftime("%Y-W%V")
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "active_support/core_ext/string/filters"
-
9
require "active_support/core_ext/array/extract_options"
-
-
9
module ActionView
-
# = Action View Text Helpers
-
9
module Helpers #:nodoc:
-
# The TextHelper module provides a set of methods for filtering, formatting
-
# and transforming strings, which can reduce the amount of inline Ruby code in
-
# your views. These helper methods extend Action View making them callable
-
# within your template files.
-
#
-
# ==== Sanitization
-
#
-
# Most text helpers that generate HTML output sanitize the given input by default,
-
# but do not escape it. This means HTML tags will appear in the page but all malicious
-
# code will be removed. Let's look at some examples using the +simple_format+ method:
-
#
-
# simple_format('<a href="http://example.com/">Example</a>')
-
# # => "<p><a href=\"http://example.com/\">Example</a></p>"
-
#
-
# simple_format('<a href="javascript:alert(\'no!\')">Example</a>')
-
# # => "<p><a>Example</a></p>"
-
#
-
# If you want to escape all content, you should invoke the +h+ method before
-
# calling the text helper.
-
#
-
# simple_format h('<a href="http://example.com/">Example</a>')
-
# # => "<p><a href=\"http://example.com/\">Example</a></p>"
-
9
module TextHelper
-
9
extend ActiveSupport::Concern
-
-
9
include SanitizeHelper
-
9
include TagHelper
-
9
include OutputSafetyHelper
-
-
# The preferred method of outputting text in your views is to use the
-
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
-
# do not operate as expected in an eRuby code block. If you absolutely must
-
# output text within a non-output code block (i.e., <% %>), you can use the concat method.
-
#
-
# <%
-
# concat "hello"
-
# # is the equivalent of <%= "hello" %>
-
#
-
# if logged_in
-
# concat "Logged in!"
-
# else
-
# concat link_to('login', action: :login)
-
# end
-
# # will either display "Logged in!" or a login link
-
# %>
-
9
def concat(string)
-
output_buffer << string
-
end
-
-
9
def safe_concat(string)
-
output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string)
-
end
-
-
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
-
# (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...")
-
# for a total length not exceeding <tt>:length</tt>.
-
#
-
# Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
-
#
-
# Pass a block if you want to show extra content when the text is truncated.
-
#
-
# The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is
-
# +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation
-
# may produce invalid HTML (such as unbalanced or incomplete tags).
-
#
-
# truncate("Once upon a time in a world far far away")
-
# # => "Once upon a time in a world..."
-
#
-
# truncate("Once upon a time in a world far far away", length: 17)
-
# # => "Once upon a ti..."
-
#
-
# truncate("Once upon a time in a world far far away", length: 17, separator: ' ')
-
# # => "Once upon a..."
-
#
-
# truncate("And they found that many people were sleeping better.", length: 25, omission: '... (continued)')
-
# # => "And they f... (continued)"
-
#
-
# truncate("<p>Once upon a time in a world far far away</p>")
-
# # => "<p>Once upon a time in a wo..."
-
#
-
# truncate("<p>Once upon a time in a world far far away</p>", escape: false)
-
# # => "<p>Once upon a time in a wo..."
-
#
-
# truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
-
# # => "Once upon a time in a wo...<a href="#">Continue</a>"
-
9
def truncate(text, options = {}, &block)
-
if text
-
length = options.fetch(:length, 30)
-
-
content = text.truncate(length, options)
-
content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content)
-
content << capture(&block) if block_given? && text.length > length
-
content
-
end
-
end
-
-
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
-
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
-
# as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
-
# '<mark>\1</mark>') or passing a block that receives each matched term. By default +text+
-
# is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false
-
# for <tt>:sanitize</tt> will turn sanitizing off.
-
#
-
# highlight('You searched for: rails', 'rails')
-
# # => You searched for: <mark>rails</mark>
-
#
-
# highlight('You searched for: rails', /for|rails/)
-
# # => You searched <mark>for</mark>: <mark>rails</mark>
-
#
-
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
-
# # => You searched for: ruby, rails, dhh
-
#
-
# highlight('You searched for: rails', ['for', 'rails'], highlighter: '<em>\1</em>')
-
# # => You searched <em>for</em>: <em>rails</em>
-
#
-
# highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
-
# # => You searched for: <a href="search?q=rails">rails</a>
-
#
-
# highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
-
# # => You searched for: <a href="search?q=rails">rails</a>
-
#
-
# highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
-
# # => <a href="javascript:alert('no!')">ruby</a> on <mark>rails</mark>
-
9
def highlight(text, phrases, options = {})
-
text = sanitize(text) if options.fetch(:sanitize, true)
-
-
if text.blank? || phrases.blank?
-
text || ""
-
else
-
match = Array(phrases).map do |p|
-
Regexp === p ? p.to_s : Regexp.escape(p)
-
end.join("|")
-
-
if block_given?
-
text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found }
-
else
-
highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
-
text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
-
end
-
end.html_safe
-
end
-
-
# Extracts an excerpt from +text+ that matches the first instance of +phrase+.
-
# The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters
-
# defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
-
# then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. Use the
-
# <tt>:separator</tt> option to choose the delimitation. The resulting string will be stripped in any case. If the +phrase+
-
# isn't found, +nil+ is returned.
-
#
-
# excerpt('This is an example', 'an', radius: 5)
-
# # => ...s is an exam...
-
#
-
# excerpt('This is an example', 'is', radius: 5)
-
# # => This is a...
-
#
-
# excerpt('This is an example', 'is')
-
# # => This is an example
-
#
-
# excerpt('This next thing is an example', 'ex', radius: 2)
-
# # => ...next...
-
#
-
# excerpt('This is also an example', 'an', radius: 8, omission: '<chop> ')
-
# # => <chop> is also an example
-
#
-
# excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1)
-
# # => ...a very beautiful...
-
9
def excerpt(text, phrase, options = {})
-
return unless text && phrase
-
-
separator = options.fetch(:separator, nil) || ""
-
case phrase
-
when Regexp
-
regex = phrase
-
else
-
regex = /#{Regexp.escape(phrase)}/i
-
end
-
-
return unless matches = text.match(regex)
-
phrase = matches[0]
-
-
unless separator.empty?
-
text.split(separator).each do |value|
-
if value.match?(regex)
-
phrase = value
-
break
-
end
-
end
-
end
-
-
first_part, second_part = text.split(phrase, 2)
-
-
prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
-
postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
-
-
affix = [first_part, separator, phrase, separator, second_part].join.strip
-
[prefix, affix, postfix].join
-
end
-
-
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
-
# +plural+ is supplied, it will use that when count is > 1, otherwise
-
# it will use the Inflector to determine the plural form for the given locale,
-
# which defaults to I18n.locale
-
#
-
# The word will be pluralized using rules defined for the locale
-
# (you must define your own inflection rules for languages other than English).
-
# See ActiveSupport::Inflector.pluralize
-
#
-
# pluralize(1, 'person')
-
# # => 1 person
-
#
-
# pluralize(2, 'person')
-
# # => 2 people
-
#
-
# pluralize(3, 'person', plural: 'users')
-
# # => 3 users
-
#
-
# pluralize(0, 'person')
-
# # => 0 people
-
#
-
# pluralize(2, 'Person', locale: :de)
-
# # => 2 Personen
-
9
def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
-
word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/)
-
singular
-
else
-
plural || singular.pluralize(locale)
-
end
-
-
"#{count || 0} #{word}"
-
end
-
-
# Wraps the +text+ into lines no longer than +line_width+ width. This method
-
# breaks on the first whitespace character that does not exceed +line_width+
-
# (which is 80 by default).
-
#
-
# word_wrap('Once upon a time')
-
# # => Once upon a time
-
#
-
# word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
-
# # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined...
-
#
-
# word_wrap('Once upon a time', line_width: 8)
-
# # => Once\nupon a\ntime
-
#
-
# word_wrap('Once upon a time', line_width: 1)
-
# # => Once\nupon\na\ntime
-
#
-
# You can also specify a custom +break_sequence+ ("\n" by default)
-
#
-
# word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n")
-
# # => Once\r\nupon\r\na\r\ntime
-
9
def word_wrap(text, line_width: 80, break_sequence: "\n")
-
text.split("\n").collect! do |line|
-
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
-
end * break_sequence
-
end
-
-
# Returns +text+ transformed into HTML using simple formatting rules.
-
# Two or more consecutive newlines(<tt>\n\n</tt> or <tt>\r\n\r\n</tt>) are
-
# considered a paragraph and wrapped in <tt><p></tt> tags. One newline
-
# (<tt>\n</tt> or <tt>\r\n</tt>) is considered a linebreak and a
-
# <tt><br /></tt> tag is appended. This method does not remove the
-
# newlines from the +text+.
-
#
-
# You can pass any HTML attributes into <tt>html_options</tt>. These
-
# will be added to all created paragraphs.
-
#
-
# ==== Options
-
# * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
-
# * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>
-
#
-
# ==== Examples
-
# my_text = "Here is some basic text...\n...with a line break."
-
#
-
# simple_format(my_text)
-
# # => "<p>Here is some basic text...\n<br />...with a line break.</p>"
-
#
-
# simple_format(my_text, {}, wrapper_tag: "div")
-
# # => "<div>Here is some basic text...\n<br />...with a line break.</div>"
-
#
-
# more_text = "We want to put a paragraph...\n\n...right there."
-
#
-
# simple_format(more_text)
-
# # => "<p>We want to put a paragraph...</p>\n\n<p>...right there.</p>"
-
#
-
# simple_format("Look ma! A class!", class: 'description')
-
# # => "<p class='description'>Look ma! A class!</p>"
-
#
-
# simple_format("<blink>Unblinkable.</blink>")
-
# # => "<p>Unblinkable.</p>"
-
#
-
# simple_format("<blink>Blinkable!</blink> It's true.", {}, sanitize: false)
-
# # => "<p><blink>Blinkable!</blink> It's true.</p>"
-
9
def simple_format(text, html_options = {}, options = {})
-
wrapper_tag = options.fetch(:wrapper_tag, :p)
-
-
text = sanitize(text) if options.fetch(:sanitize, true)
-
paragraphs = split_paragraphs(text)
-
-
if paragraphs.empty?
-
content_tag(wrapper_tag, nil, html_options)
-
else
-
paragraphs.map! { |paragraph|
-
content_tag(wrapper_tag, raw(paragraph), html_options)
-
}.join("\n\n").html_safe
-
end
-
end
-
-
# Creates a Cycle object whose _to_s_ method cycles through elements of an
-
# array every time it is called. This can be used for example, to alternate
-
# classes for table rows. You can use named cycles to allow nesting in loops.
-
# Passing a Hash as the last parameter with a <tt>:name</tt> key will create a
-
# named cycle. The default name for a cycle without a +:name+ key is
-
# <tt>"default"</tt>. You can manually reset a cycle by calling reset_cycle
-
# and passing the name of the cycle. The current cycle string can be obtained
-
# anytime using the current_cycle method.
-
#
-
# # Alternate CSS classes for even and odd numbers...
-
# @items = [1,2,3,4]
-
# <table>
-
# <% @items.each do |item| %>
-
# <tr class="<%= cycle("odd", "even") -%>">
-
# <td><%= item %></td>
-
# </tr>
-
# <% end %>
-
# </table>
-
#
-
#
-
# # Cycle CSS classes for rows, and text colors for values within each row
-
# @items = x = [{first: 'Robert', middle: 'Daniel', last: 'James'},
-
# {first: 'Emily', middle: 'Shannon', maiden: 'Pike', last: 'Hicks'},
-
# {first: 'June', middle: 'Dae', last: 'Jones'}]
-
# <% @items.each do |item| %>
-
# <tr class="<%= cycle("odd", "even", name: "row_class") -%>">
-
# <td>
-
# <% item.values.each do |value| %>
-
# <%# Create a named cycle "colors" %>
-
# <span style="color:<%= cycle("red", "green", "blue", name: "colors") -%>">
-
# <%= value %>
-
# </span>
-
# <% end %>
-
# <% reset_cycle("colors") %>
-
# </td>
-
# </tr>
-
# <% end %>
-
9
def cycle(first_value, *values)
-
options = values.extract_options!
-
name = options.fetch(:name, "default")
-
-
values.unshift(*first_value)
-
-
cycle = get_cycle(name)
-
unless cycle && cycle.values == values
-
cycle = set_cycle(name, Cycle.new(*values))
-
end
-
cycle.to_s
-
end
-
-
# Returns the current cycle string after a cycle has been started. Useful
-
# for complex table highlighting or any other design need which requires
-
# the current cycle string in more than one place.
-
#
-
# # Alternate background colors
-
# @items = [1,2,3,4]
-
# <% @items.each do |item| %>
-
# <div style="background-color:<%= cycle("red","white","blue") %>">
-
# <span style="background-color:<%= current_cycle %>"><%= item %></span>
-
# </div>
-
# <% end %>
-
9
def current_cycle(name = "default")
-
cycle = get_cycle(name)
-
cycle.current_value if cycle
-
end
-
-
# Resets a cycle so that it starts from the first element the next time
-
# it is called. Pass in +name+ to reset a named cycle.
-
#
-
# # Alternate CSS classes for even and odd numbers...
-
# @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
-
# <table>
-
# <% @items.each do |item| %>
-
# <tr class="<%= cycle("even", "odd") -%>">
-
# <% item.each do |value| %>
-
# <span style="color:<%= cycle("#333", "#666", "#999", name: "colors") -%>">
-
# <%= value %>
-
# </span>
-
# <% end %>
-
#
-
# <% reset_cycle("colors") %>
-
# </tr>
-
# <% end %>
-
# </table>
-
9
def reset_cycle(name = "default")
-
cycle = get_cycle(name)
-
cycle.reset if cycle
-
end
-
-
9
class Cycle #:nodoc:
-
9
attr_reader :values
-
-
9
def initialize(first_value, *values)
-
@values = values.unshift(first_value)
-
reset
-
end
-
-
9
def reset
-
@index = 0
-
end
-
-
9
def current_value
-
@values[previous_index].to_s
-
end
-
-
9
def to_s
-
value = @values[@index].to_s
-
@index = next_index
-
value
-
end
-
-
9
private
-
9
def next_index
-
step_index(1)
-
end
-
-
9
def previous_index
-
step_index(-1)
-
end
-
-
9
def step_index(n)
-
(@index + n) % @values.size
-
end
-
end
-
-
9
private
-
# The cycle helpers need to store the cycles in a place that is
-
# guaranteed to be reset every time a page is rendered, so it
-
# uses an instance variable of ActionView::Base.
-
9
def get_cycle(name)
-
@_cycles = Hash.new unless defined?(@_cycles)
-
@_cycles[name]
-
end
-
-
9
def set_cycle(name, cycle_object)
-
@_cycles = Hash.new unless defined?(@_cycles)
-
@_cycles[name] = cycle_object
-
end
-
-
9
def split_paragraphs(text)
-
return [] if text.blank?
-
-
text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t|
-
t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t
-
end
-
end
-
-
9
def cut_excerpt_part(part_position, part, separator, options)
-
return "", "" unless part
-
-
radius = options.fetch(:radius, 100)
-
omission = options.fetch(:omission, "...")
-
-
part = part.split(separator)
-
part.delete("")
-
affix = part.size > radius ? omission : ""
-
-
part = if part_position == :first
-
drop_index = [part.length - radius, 0].max
-
part.drop(drop_index)
-
else
-
part.first(radius)
-
end
-
-
return affix, part.join(separator)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "action_view/helpers/tag_helper"
-
9
require "active_support/core_ext/string/access"
-
9
require "i18n/exceptions"
-
-
9
module ActionView
-
# = Action View Translation Helpers
-
9
module Helpers #:nodoc:
-
9
module TranslationHelper
-
9
extend ActiveSupport::Concern
-
-
9
include TagHelper
-
-
9
included do
-
12
mattr_accessor :debug_missing_translation, default: true
-
end
-
-
# Delegates to <tt>I18n#translate</tt> but also performs three additional
-
# functions.
-
#
-
# First, it will ensure that any thrown +MissingTranslation+ messages will
-
# be rendered as inline spans that:
-
#
-
# * Have a <tt>translation-missing</tt> class applied
-
# * Contain the missing key as the value of the +title+ attribute
-
# * Have a titleized version of the last key segment as text
-
#
-
# For example, the value returned for the missing translation key
-
# <tt>"blog.post.title"</tt> will be:
-
#
-
# <span
-
# class="translation_missing"
-
# title="translation missing: en.blog.post.title">Title</span>
-
#
-
# This allows for views to display rather reasonable strings while still
-
# giving developers a way to find missing translations.
-
#
-
# If you would prefer missing translations to raise an error, you can
-
# opt out of span-wrapping behavior globally by setting
-
# <tt>ActionView::Base.raise_on_missing_translations = true</tt> or
-
# individually by passing <tt>raise: true</tt> as an option to
-
# <tt>translate</tt>.
-
#
-
# Second, if the key starts with a period <tt>translate</tt> will scope
-
# the key by the current partial. Calling <tt>translate(".foo")</tt> from
-
# the <tt>people/index.html.erb</tt> template is equivalent to calling
-
# <tt>translate("people.index.foo")</tt>. This makes it less
-
# repetitive to translate many keys within the same partial and provides
-
# a convention to scope keys consistently.
-
#
-
# Third, the translation will be marked as <tt>html_safe</tt> if the key
-
# has the suffix "_html" or the last element of the key is "html". Calling
-
# <tt>translate("footer_html")</tt> or <tt>translate("footer.html")</tt>
-
# will return an HTML safe string that won't be escaped by other HTML
-
# helper methods. This naming convention helps to identify translations
-
# that include HTML tags so that you know what kind of output to expect
-
# when you call translate in a template and translators know which keys
-
# they can provide HTML values for.
-
9
def translate(key, **options)
-
unless options[:default].nil?
-
remaining_defaults = Array.wrap(options.delete(:default)).compact
-
options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
-
end
-
-
# If the user has explicitly decided to NOT raise errors, pass that option to I18n.
-
# Otherwise, tell I18n to raise an exception, which we rescue further in this method.
-
# Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
-
if options[:raise] == false
-
raise_error = false
-
i18n_raise = false
-
else
-
raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
-
i18n_raise = true
-
end
-
-
if html_safe_translation_key?(key)
-
html_safe_options = options.dup
-
options.except(*I18n::RESERVED_KEYS).each do |name, value|
-
unless name == :count && value.is_a?(Numeric)
-
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
-
end
-
end
-
translation = I18n.translate(scope_key_by_partial(key), **html_safe_options.merge(raise: i18n_raise))
-
if translation.respond_to?(:map)
-
translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
-
else
-
translation.respond_to?(:html_safe) ? translation.html_safe : translation
-
end
-
else
-
I18n.translate(scope_key_by_partial(key), **options.merge(raise: i18n_raise))
-
end
-
rescue I18n::MissingTranslationData => e
-
if remaining_defaults.present?
-
translate remaining_defaults.shift, **options.merge(default: remaining_defaults)
-
else
-
raise e if raise_error
-
-
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
-
title = +"translation missing: #{keys.join('.')}"
-
-
interpolations = options.except(:default, :scope)
-
if interpolations.any?
-
title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ")
-
end
-
-
return title unless ActionView::Base.debug_missing_translation
-
-
content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
-
end
-
end
-
9
alias :t :translate
-
-
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
-
#
-
# See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
-
# for more information.
-
9
def localize(object, **options)
-
I18n.localize(object, **options)
-
end
-
9
alias :l :localize
-
-
9
private
-
9
def scope_key_by_partial(key)
-
stringified_key = key.to_s
-
if stringified_key.start_with?(".")
-
if @current_template&.virtual_path
-
@_scope_key_by_partial_cache ||= {}
-
@_scope_key_by_partial_cache[@current_template.virtual_path] ||= @current_template.virtual_path.gsub(%r{/_?}, ".")
-
"#{@_scope_key_by_partial_cache[@current_template.virtual_path]}#{stringified_key}"
-
else
-
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
-
end
-
else
-
key
-
end
-
end
-
-
9
def html_safe_translation_key?(key)
-
/(?:_|\b)html\z/.match?(key.to_s)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "action_view/helpers/javascript_helper"
-
9
require "active_support/core_ext/array/access"
-
9
require "active_support/core_ext/hash/keys"
-
9
require "active_support/core_ext/string/output_safety"
-
-
9
module ActionView
-
# = Action View URL Helpers
-
9
module Helpers #:nodoc:
-
# Provides a set of methods for making links and getting URLs that
-
# depend on the routing subsystem (see ActionDispatch::Routing).
-
# This allows you to use the same format for links in views
-
# and controllers.
-
9
module UrlHelper
-
# This helper may be included in any class that includes the
-
# URL helpers of a routes (routes.url_helpers). Some methods
-
# provided here will only work in the context of a request
-
# (link_to_unless_current, for instance), which must be provided
-
# as a method called #request on the context.
-
9
BUTTON_TAG_METHOD_VERBS = %w{patch put delete}
-
9
extend ActiveSupport::Concern
-
-
9
include TagHelper
-
-
9
module ClassMethods
-
9
def _url_for_modules
-
3
ActionView::RoutingUrlFor
-
end
-
end
-
-
# Basic implementation of url_for to allow use helpers without routes existence
-
9
def url_for(options = nil) # :nodoc:
-
case options
-
when String
-
options
-
when :back
-
_back_url
-
else
-
raise ArgumentError, "arguments passed to url_for can't be handled. Please require " \
-
"routes or provide your own implementation"
-
end
-
end
-
-
9
def _back_url # :nodoc:
-
_filtered_referrer || "javascript:history.back()"
-
end
-
9
private :_back_url
-
-
9
def _filtered_referrer # :nodoc:
-
if controller.respond_to?(:request)
-
referrer = controller.request.env["HTTP_REFERER"]
-
if referrer && URI(referrer).scheme != "javascript"
-
referrer
-
end
-
end
-
rescue URI::InvalidURIError
-
end
-
9
private :_filtered_referrer
-
-
# Creates an anchor element of the given +name+ using a URL created by the set of +options+.
-
# See the valid options in the documentation for +url_for+. It's also possible to
-
# pass a \String instead of an options hash, which generates an anchor element that uses the
-
# value of the \String as the href for the link. Using a <tt>:back</tt> \Symbol instead
-
# of an options hash will generate a link to the referrer (a JavaScript back link
-
# will be used in place of a referrer if none exists). If +nil+ is passed as the name
-
# the value of the link itself will become the name.
-
#
-
# ==== Signatures
-
#
-
# link_to(body, url, html_options = {})
-
# # url is a String; you can use URL helpers like
-
# # posts_path
-
#
-
# link_to(body, url_options = {}, html_options = {})
-
# # url_options, except :method, is passed to url_for
-
#
-
# link_to(options = {}, html_options = {}) do
-
# # name
-
# end
-
#
-
# link_to(url, html_options = {}) do
-
# # name
-
# end
-
#
-
# ==== Options
-
# * <tt>:data</tt> - This option can be used to add custom data attributes.
-
# * <tt>method: symbol of HTTP verb</tt> - This modifier will dynamically
-
# create an HTML form and immediately submit the form for processing using
-
# the HTTP verb specified. Useful for having links perform a POST operation
-
# in dangerous actions like deleting a record (which search bots can follow
-
# while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
-
# Note that if the user has JavaScript disabled, the request will fall back
-
# to using GET. If <tt>href: '#'</tt> is used and the user has JavaScript
-
# disabled clicking the link will have no effect. If you are relying on the
-
# POST behavior, you should check for it in your controller's action by using
-
# the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>patch?</tt>, or <tt>put?</tt>.
-
# * <tt>remote: true</tt> - This will allow the unobtrusive JavaScript
-
# driver to make an Ajax request to the URL in question instead of following
-
# the link. The drivers each provide mechanisms for listening for the
-
# completion of the Ajax request and performing JavaScript operations once
-
# they're complete
-
#
-
# ==== Data attributes
-
#
-
# * <tt>confirm: 'question?'</tt> - This will allow the unobtrusive JavaScript
-
# driver to prompt with the question specified (in this case, the
-
# resulting text would be <tt>question?</tt>. If the user accepts, the
-
# link is processed normally, otherwise no action is taken.
-
# * <tt>:disable_with</tt> - Value of this parameter will be used as the
-
# name for a disabled version of the link. This feature is provided by
-
# the unobtrusive JavaScript driver.
-
#
-
# ==== Examples
-
# Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
-
# and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base
-
# your application on resources and use
-
#
-
# link_to "Profile", profile_path(@profile)
-
# # => <a href="/profiles/1">Profile</a>
-
#
-
# or the even pithier
-
#
-
# link_to "Profile", @profile
-
# # => <a href="/profiles/1">Profile</a>
-
#
-
# in place of the older more verbose, non-resource-oriented
-
#
-
# link_to "Profile", controller: "profiles", action: "show", id: @profile
-
# # => <a href="/profiles/show/1">Profile</a>
-
#
-
# Similarly,
-
#
-
# link_to "Profiles", profiles_path
-
# # => <a href="/profiles">Profiles</a>
-
#
-
# is better than
-
#
-
# link_to "Profiles", controller: "profiles"
-
# # => <a href="/profiles">Profiles</a>
-
#
-
# When name is +nil+ the href is presented instead
-
#
-
# link_to nil, "http://example.com"
-
# # => <a href="http://www.example.com">http://www.example.com</a>
-
#
-
# You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
-
#
-
# <%= link_to(@profile) do %>
-
# <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
-
# <% end %>
-
# # => <a href="/profiles/1">
-
# <strong>David</strong> -- <span>Check it out!</span>
-
# </a>
-
#
-
# Classes and ids for CSS are easy to produce:
-
#
-
# link_to "Articles", articles_path, id: "news", class: "article"
-
# # => <a href="/articles" class="article" id="news">Articles</a>
-
#
-
# Be careful when using the older argument style, as an extra literal hash is needed:
-
#
-
# link_to "Articles", { controller: "articles" }, id: "news", class: "article"
-
# # => <a href="/articles" class="article" id="news">Articles</a>
-
#
-
# Leaving the hash off gives the wrong link:
-
#
-
# link_to "WRONG!", controller: "articles", id: "news", class: "article"
-
# # => <a href="/articles/index/news?class=article">WRONG!</a>
-
#
-
# +link_to+ can also produce links with anchors or query strings:
-
#
-
# link_to "Comment wall", profile_path(@profile, anchor: "wall")
-
# # => <a href="/profiles/1#wall">Comment wall</a>
-
#
-
# link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails"
-
# # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>
-
#
-
# link_to "Nonsense search", searches_path(foo: "bar", baz: "quux")
-
# # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a>
-
#
-
# The only option specific to +link_to+ (<tt>:method</tt>) is used as follows:
-
#
-
# link_to("Destroy", "http://www.example.com", method: :delete)
-
# # => <a href='http://www.example.com' rel="nofollow" data-method="delete">Destroy</a>
-
#
-
# You can also use custom data attributes using the <tt>:data</tt> option:
-
#
-
# link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
-
# # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a>
-
#
-
# Also you can set any link attributes such as <tt>target</tt>, <tt>rel</tt>, <tt>type</tt>:
-
#
-
# link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow"
-
# # => <a href="http://www.rubyonrails.org/" target="_blank" rel="nofollow">External link</a>
-
9
def link_to(name = nil, options = nil, html_options = nil, &block)
-
html_options, options, name = options, name, block if block_given?
-
options ||= {}
-
-
html_options = convert_options_to_data_attributes(options, html_options)
-
-
url = url_for(options)
-
html_options["href"] ||= url
-
-
content_tag("a", name || url, html_options, &block)
-
end
-
-
# Generates a form containing a single button that submits to the URL created
-
# by the set of +options+. This is the safest method to ensure links that
-
# cause changes to your data are not triggered by search bots or accelerators.
-
# If the HTML button does not work with your layout, you can also consider
-
# using the +link_to+ method with the <tt>:method</tt> modifier as described in
-
# the +link_to+ documentation.
-
#
-
# By default, the generated form element has a class name of <tt>button_to</tt>
-
# to allow styling of the form itself and its children. This can be changed
-
# using the <tt>:form_class</tt> modifier within +html_options+. You can control
-
# the form submission and input element behavior using +html_options+.
-
# This method accepts the <tt>:method</tt> modifier described in the +link_to+ documentation.
-
# If no <tt>:method</tt> modifier is given, it will default to performing a POST operation.
-
# You can also disable the button by passing <tt>disabled: true</tt> in +html_options+.
-
# If you are using RESTful routes, you can pass the <tt>:method</tt>
-
# to change the HTTP verb used to submit the form.
-
#
-
# ==== Options
-
# The +options+ hash accepts the same options as +url_for+.
-
#
-
# There are a few special +html_options+:
-
# * <tt>:method</tt> - \Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
-
# <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
-
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
-
# * <tt>:data</tt> - This option can be used to add custom data attributes.
-
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
-
# submit behavior. By default this behavior is an ajax submit.
-
# * <tt>:form</tt> - This hash will be form attributes
-
# * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
-
# be placed
-
# * <tt>:params</tt> - \Hash of parameters to be rendered as hidden fields within the form.
-
#
-
# ==== Data attributes
-
#
-
# * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
-
# prompt with the question specified. If the user accepts, the link is
-
# processed normally, otherwise no action is taken.
-
# * <tt>:disable_with</tt> - Value of this parameter will be
-
# used as the value for a disabled version of the submit
-
# button when the form is submitted. This feature is provided
-
# by the unobtrusive JavaScript driver.
-
#
-
# ==== Examples
-
# <%= button_to "New", action: "new" %>
-
# # => "<form method="post" action="/controller/new" class="button_to">
-
# # <input value="New" type="submit" />
-
# # </form>"
-
#
-
# <%= button_to "New", new_article_path %>
-
# # => "<form method="post" action="/articles/new" class="button_to">
-
# # <input value="New" type="submit" />
-
# # </form>"
-
#
-
# <%= button_to [:make_happy, @user] do %>
-
# Make happy <strong><%= @user.name %></strong>
-
# <% end %>
-
# # => "<form method="post" action="/users/1/make_happy" class="button_to">
-
# # <button type="submit">
-
# # Make happy <strong><%= @user.name %></strong>
-
# # </button>
-
# # </form>"
-
#
-
# <%= button_to "New", { action: "new" }, form_class: "new-thing" %>
-
# # => "<form method="post" action="/controller/new" class="new-thing">
-
# # <input value="New" type="submit" />
-
# # </form>"
-
#
-
#
-
# <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %>
-
# # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
-
# # <input value="Create" type="submit" />
-
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
-
# # </form>"
-
#
-
#
-
# <%= button_to "Delete Image", { action: "delete", id: @image.id },
-
# method: :delete, data: { confirm: "Are you sure?" } %>
-
# # => "<form method="post" action="/images/delete/1" class="button_to">
-
# # <input type="hidden" name="_method" value="delete" />
-
# # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
-
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
-
# # </form>"
-
#
-
#
-
# <%= button_to('Destroy', 'http://www.example.com',
-
# method: :delete, remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %>
-
# # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
-
# # <input name='_method' value='delete' type='hidden' />
-
# # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
-
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
-
# # </form>"
-
# #
-
9
def button_to(name = nil, options = nil, html_options = nil, &block)
-
html_options, options = options, name if block_given?
-
options ||= {}
-
html_options ||= {}
-
html_options = html_options.stringify_keys
-
-
url = options.is_a?(String) ? options : url_for(options)
-
remote = html_options.delete("remote")
-
params = html_options.delete("params")
-
-
method = html_options.delete("method").to_s
-
method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".html_safe
-
-
form_method = method == "get" ? "get" : "post"
-
form_options = html_options.delete("form") || {}
-
form_options[:class] ||= html_options.delete("form_class") || "button_to"
-
form_options[:method] = form_method
-
form_options[:action] = url
-
form_options[:'data-remote'] = true if remote
-
-
request_token_tag = if form_method == "post"
-
request_method = method.empty? ? "post" : method
-
token_tag(nil, form_options: { action: url, method: request_method })
-
else
-
""
-
end
-
-
html_options = convert_options_to_data_attributes(options, html_options)
-
html_options["type"] = "submit"
-
-
button = if block_given?
-
content_tag("button", html_options, &block)
-
else
-
html_options["value"] = name || url
-
tag("input", html_options)
-
end
-
-
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
-
if params
-
to_form_params(params).each do |param|
-
inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value])
-
end
-
end
-
content_tag("form", inner_tags, form_options)
-
end
-
-
# Creates a link tag of the given +name+ using a URL created by the set of
-
# +options+ unless the current request URI is the same as the links, in
-
# which case only the name is returned (or the given block is yielded, if
-
# one exists). You can give +link_to_unless_current+ a block which will
-
# specialize the default behavior (e.g., show a "Start Here" link rather
-
# than the link's text).
-
#
-
# ==== Examples
-
# Let's say you have a navigation menu...
-
#
-
# <ul id="navbar">
-
# <li><%= link_to_unless_current("Home", { action: "index" }) %></li>
-
# <li><%= link_to_unless_current("About Us", { action: "about" }) %></li>
-
# </ul>
-
#
-
# If in the "about" action, it will render...
-
#
-
# <ul id="navbar">
-
# <li><a href="/controller/index">Home</a></li>
-
# <li>About Us</li>
-
# </ul>
-
#
-
# ...but if in the "index" action, it will render:
-
#
-
# <ul id="navbar">
-
# <li>Home</li>
-
# <li><a href="/controller/about">About Us</a></li>
-
# </ul>
-
#
-
# The implicit block given to +link_to_unless_current+ is evaluated if the current
-
# action is the action given. So, if we had a comments page and wanted to render a
-
# "Go Back" link instead of a link to the comments page, we could do something like this...
-
#
-
# <%=
-
# link_to_unless_current("Comment", { controller: "comments", action: "new" }) do
-
# link_to("Go back", { controller: "posts", action: "index" })
-
# end
-
# %>
-
9
def link_to_unless_current(name, options = {}, html_options = {}, &block)
-
link_to_unless current_page?(options), name, options, html_options, &block
-
end
-
-
# Creates a link tag of the given +name+ using a URL created by the set of
-
# +options+ unless +condition+ is true, in which case only the name is
-
# returned. To specialize the default behavior (i.e., show a login link rather
-
# than just the plaintext link text), you can pass a block that
-
# accepts the name or the full argument list for +link_to_unless+.
-
#
-
# ==== Examples
-
# <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %>
-
# # If the user is logged in...
-
# # => <a href="/controller/reply/">Reply</a>
-
#
-
# <%=
-
# link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name|
-
# link_to(name, { controller: "accounts", action: "signup" })
-
# end
-
# %>
-
# # If the user is logged in...
-
# # => <a href="/controller/reply/">Reply</a>
-
# # If not...
-
# # => <a href="/accounts/signup">Reply</a>
-
9
def link_to_unless(condition, name, options = {}, html_options = {}, &block)
-
link_to_if !condition, name, options, html_options, &block
-
end
-
-
# Creates a link tag of the given +name+ using a URL created by the set of
-
# +options+ if +condition+ is true, otherwise only the name is
-
# returned. To specialize the default behavior, you can pass a block that
-
# accepts the name or the full argument list for +link_to_if+.
-
#
-
# ==== Examples
-
# <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %>
-
# # If the user isn't logged in...
-
# # => <a href="/sessions/new/">Login</a>
-
#
-
# <%=
-
# link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do
-
# link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user })
-
# end
-
# %>
-
# # If the user isn't logged in...
-
# # => <a href="/sessions/new/">Login</a>
-
# # If they are logged in...
-
# # => <a href="/accounts/show/3">my_username</a>
-
9
def link_to_if(condition, name, options = {}, html_options = {}, &block)
-
if condition
-
link_to(name, options, html_options)
-
else
-
if block_given?
-
block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
-
else
-
ERB::Util.html_escape(name)
-
end
-
end
-
end
-
-
# Creates a mailto link tag to the specified +email_address+, which is
-
# also used as the name of the link unless +name+ is specified. Additional
-
# HTML attributes for the link can be passed in +html_options+.
-
#
-
# +mail_to+ has several methods for customizing the email itself by
-
# passing special keys to +html_options+.
-
#
-
# ==== Options
-
# * <tt>:subject</tt> - Preset the subject line of the email.
-
# * <tt>:body</tt> - Preset the body of the email.
-
# * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
-
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
-
# * <tt>:reply_to</tt> - Preset the Reply-To field of the email.
-
#
-
# ==== Obfuscation
-
# Prior to Rails 4.0, +mail_to+ provided options for encoding the address
-
# in order to hinder email harvesters. To take advantage of these options,
-
# install the +actionview-encoded_mail_to+ gem.
-
#
-
# ==== Examples
-
# mail_to "me@domain.com"
-
# # => <a href="mailto:me@domain.com">me@domain.com</a>
-
#
-
# mail_to "me@domain.com", "My email"
-
# # => <a href="mailto:me@domain.com">My email</a>
-
#
-
# mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com",
-
# subject: "This is an example email"
-
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
-
#
-
# You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
-
#
-
# <%= mail_to "me@domain.com" do %>
-
# <strong>Email me:</strong> <span>me@domain.com</span>
-
# <% end %>
-
# # => <a href="mailto:me@domain.com">
-
# <strong>Email me:</strong> <span>me@domain.com</span>
-
# </a>
-
9
def mail_to(email_address, name = nil, html_options = {}, &block)
-
html_options, name = name, nil if block_given?
-
html_options = (html_options || {}).stringify_keys
-
-
extras = %w{ cc bcc body subject reply_to }.map! { |item|
-
option = html_options.delete(item).presence || next
-
"#{item.dasherize}=#{ERB::Util.url_encode(option)}"
-
}.compact
-
extras = extras.empty? ? "" : "?" + extras.join("&")
-
-
encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
-
html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
-
-
content_tag("a", name || email_address, html_options, &block)
-
end
-
-
# True if the current request URI was generated by the given +options+.
-
#
-
# ==== Examples
-
# Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc&page=1</tt> action.
-
#
-
# current_page?(action: 'process')
-
# # => false
-
#
-
# current_page?(action: 'checkout')
-
# # => true
-
#
-
# current_page?(controller: 'library', action: 'checkout')
-
# # => false
-
#
-
# current_page?(controller: 'shop', action: 'checkout')
-
# # => true
-
#
-
# current_page?(controller: 'shop', action: 'checkout', order: 'asc')
-
# # => false
-
#
-
# current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1')
-
# # => true
-
#
-
# current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2')
-
# # => false
-
#
-
# current_page?('http://www.example.com/shop/checkout')
-
# # => true
-
#
-
# current_page?('http://www.example.com/shop/checkout', check_parameters: true)
-
# # => false
-
#
-
# current_page?('/shop/checkout')
-
# # => true
-
#
-
# current_page?('http://www.example.com/shop/checkout?order=desc&page=1')
-
# # => true
-
#
-
# Let's say we're in the <tt>http://www.example.com/products</tt> action with method POST in case of invalid product.
-
#
-
# current_page?(controller: 'product', action: 'index')
-
# # => false
-
#
-
# We can also pass in the symbol arguments instead of strings.
-
#
-
9
def current_page?(options, check_parameters: false)
-
unless request
-
raise "You cannot use helpers that need to determine the current " \
-
"page unless your view context provides a Request object " \
-
"in a #request method"
-
end
-
-
return false unless request.get? || request.head?
-
-
check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
-
url_string = URI::DEFAULT_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
-
-
# We ignore any extra parameters in the request_uri if the
-
# submitted URL doesn't have any either. This lets the function
-
# work with things like ?order=asc
-
# the behaviour can be disabled with check_parameters: true
-
request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
-
request_uri = URI::DEFAULT_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
-
-
if url_string.start_with?("/") && url_string != "/"
-
url_string.chomp!("/")
-
request_uri.chomp!("/")
-
end
-
-
if %r{^\w+://}.match?(url_string)
-
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
-
else
-
url_string == request_uri
-
end
-
end
-
-
# Creates an SMS anchor link tag to the specified +phone_number+, which is
-
# also used as the name of the link unless +name+ is specified. Additional
-
# HTML attributes for the link can be passed in +html_options+.
-
#
-
# When clicked, an SMS message is prepopulated with the passed phone number
-
# and optional +body+ value.
-
#
-
# +sms_to+ has a +body+ option for customizing the SMS message itself by
-
# passing special keys to +html_options+.
-
#
-
# ==== Options
-
# * <tt>:body</tt> - Preset the body of the message.
-
#
-
# ==== Examples
-
# sms_to "5155555785"
-
# # => <a href="sms:5155555785;">5155555785</a>
-
#
-
# sms_to "5155555785", "Text me"
-
# # => <a href="sms:5155555785;">Text me</a>
-
#
-
# sms_to "5155555785", "Text me",
-
# body: "Hello Jim I have a question about your product."
-
# # => <a href="sms:5155555785;?body=Hello%20Jim%20I%20have%20a%20question%20about%20your%20product">Text me</a>
-
#
-
# You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
-
#
-
# <%= sms_to "5155555785" do %>
-
# <strong>Text me:</strong>
-
# <% end %>
-
# # => <a href="sms:5155555785;">
-
# <strong>Text me:</strong>
-
# </a>
-
9
def sms_to(phone_number, name = nil, html_options = {}, &block)
-
html_options, name = name, nil if block_given?
-
html_options = (html_options || {}).stringify_keys
-
-
extras = %w{ body }.map! { |item|
-
option = html_options.delete(item).presence || next
-
"#{item.dasherize}=#{ERB::Util.url_encode(option)}"
-
}.compact
-
extras = extras.empty? ? "" : "?&" + extras.join("&")
-
-
encoded_phone_number = ERB::Util.url_encode(phone_number)
-
html_options["href"] = "sms:#{encoded_phone_number};#{extras}"
-
-
content_tag("a", name || phone_number, html_options, &block)
-
end
-
-
# Creates a TEL anchor link tag to the specified +phone_number+, which is
-
# also used as the name of the link unless +name+ is specified. Additional
-
# HTML attributes for the link can be passed in +html_options+.
-
#
-
# When clicked, the default app to make calls is opened, and it
-
# is prepopulated with the passed phone number and optional
-
# +country_code+ value.
-
#
-
# +phone_to+ has an optional +country_code+ option which automatically adds the country
-
# code as well as the + sign in the phone numer that gets prepopulated,
-
# for example if +country_code: "01"+ +\+01+ will be prepended to the
-
# phone numer, by passing special keys to +html_options+.
-
#
-
# ==== Options
-
# * <tt>:country_code</tt> - Prepends the country code to the number
-
#
-
# ==== Examples
-
# phone_to "1234567890"
-
# # => <a href="tel:1234567890">1234567890</a>
-
#
-
# phone_to "1234567890", "Phone me"
-
# # => <a href="tel:134567890">Phone me</a>
-
#
-
# phone_to "1234567890", "Phone me", country_code: "01"
-
# # => <a href="tel:+015155555785">Phone me</a>
-
#
-
# You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
-
#
-
# <%= phone_to "1234567890" do %>
-
# <strong>Phone me:</strong>
-
# <% end %>
-
# # => <a href="tel:1234567890">
-
# <strong>Phone me:</strong>
-
# </a>
-
9
def phone_to(phone_number, name = nil, html_options = {}, &block)
-
html_options, name = name, nil if block_given?
-
html_options = (html_options || {}).stringify_keys
-
-
country_code = html_options.delete("country_code").presence
-
country_code = country_code.nil? ? "" : "+#{ERB::Util.url_encode(country_code)}"
-
-
encoded_phone_number = ERB::Util.url_encode(phone_number)
-
html_options["href"] = "tel:#{country_code}#{encoded_phone_number}"
-
-
content_tag("a", name || phone_number, html_options, &block)
-
end
-
-
9
private
-
9
def convert_options_to_data_attributes(options, html_options)
-
if html_options
-
html_options = html_options.stringify_keys
-
html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)
-
-
method = html_options.delete("method")
-
-
add_method_to_attributes!(html_options, method) if method
-
-
html_options
-
else
-
link_to_remote_options?(options) ? { "data-remote" => "true" } : {}
-
end
-
end
-
-
9
def link_to_remote_options?(options)
-
if options.is_a?(Hash)
-
options.delete("remote") || options.delete(:remote)
-
end
-
end
-
-
9
def add_method_to_attributes!(html_options, method)
-
if method_not_get_method?(method) && !html_options["rel"]&.match?(/nofollow/)
-
if html_options["rel"].blank?
-
html_options["rel"] = "nofollow"
-
else
-
html_options["rel"] = "#{html_options["rel"]} nofollow"
-
end
-
end
-
html_options["data-method"] = method
-
end
-
-
9
STRINGIFIED_COMMON_METHODS = {
-
get: "get",
-
delete: "delete",
-
patch: "patch",
-
post: "post",
-
put: "put",
-
}.freeze
-
-
9
def method_not_get_method?(method)
-
return false unless method
-
(STRINGIFIED_COMMON_METHODS[method] || method.to_s.downcase) != "get"
-
end
-
-
9
def token_tag(token = nil, form_options: {})
-
if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
-
token ||= form_authenticity_token(form_options: form_options)
-
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
-
else
-
""
-
end
-
end
-
-
9
def method_tag(method)
-
tag("input", type: "hidden", name: "_method", value: method.to_s)
-
end
-
-
# Returns an array of hashes each containing :name and :value keys
-
# suitable for use as the names and values of form input fields:
-
#
-
# to_form_params(name: 'David', nationality: 'Danish')
-
# # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
-
#
-
# to_form_params(country: { name: 'Denmark' })
-
# # => [{name: 'country[name]', value: 'Denmark'}]
-
#
-
# to_form_params(countries: ['Denmark', 'Sweden']})
-
# # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}]
-
#
-
# An optional namespace can be passed to enclose key names:
-
#
-
# to_form_params({ name: 'Denmark' }, 'country')
-
# # => [{name: 'country[name]', value: 'Denmark'}]
-
9
def to_form_params(attribute, namespace = nil)
-
attribute = if attribute.respond_to?(:permitted?)
-
attribute.to_h
-
else
-
attribute
-
end
-
-
params = []
-
case attribute
-
when Hash
-
attribute.each do |key, value|
-
prefix = namespace ? "#{namespace}[#{key}]" : key
-
params.push(*to_form_params(value, prefix))
-
end
-
when Array
-
array_prefix = "#{namespace}[]"
-
attribute.each do |value|
-
params.push(*to_form_params(value, array_prefix))
-
end
-
else
-
params << { name: namespace.to_s, value: attribute.to_param }
-
end
-
-
params.sort_by { |pair| pair[:name] }
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "action_view/rendering"
-
9
require "active_support/core_ext/module/redefine_method"
-
-
9
module ActionView
-
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
-
# repeated setups. The inclusion pattern has pages that look like this:
-
#
-
# <%= render "shared/header" %>
-
# Hello World
-
# <%= render "shared/footer" %>
-
#
-
# This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
-
# and if you ever want to change the structure of these two includes, you'll have to change all the templates.
-
#
-
# With layouts, you can flip it around and have the common structure know where to insert changing content. This means
-
# that the header and footer are only mentioned in one place, like this:
-
#
-
# // The header part of this layout
-
# <%= yield %>
-
# // The footer part of this layout
-
#
-
# And then you have content pages that look like this:
-
#
-
# hello world
-
#
-
# At rendering time, the content page is computed and then inserted in the layout, like this:
-
#
-
# // The header part of this layout
-
# hello world
-
# // The footer part of this layout
-
#
-
# == Accessing shared variables
-
#
-
# Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
-
# references that won't materialize before rendering time:
-
#
-
# <h1><%= @page_title %></h1>
-
# <%= yield %>
-
#
-
# ...and content pages that fulfill these references _at_ rendering time:
-
#
-
# <% @page_title = "Welcome" %>
-
# Off-world colonies offers you a chance to start a new life
-
#
-
# The result after rendering is:
-
#
-
# <h1>Welcome</h1>
-
# Off-world colonies offers you a chance to start a new life
-
#
-
# == Layout assignment
-
#
-
# You can either specify a layout declaratively (using the #layout class method) or give
-
# it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
-
# If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
-
#
-
# For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
-
# that template will be used for all actions in PostsController and controllers inheriting
-
# from PostsController.
-
#
-
# If you use a module, for instance Weblog::PostsController, you will need a template named
-
# <tt>app/views/layouts/weblog/posts.html.erb</tt>.
-
#
-
# Since all your controllers inherit from ApplicationController, they will use
-
# <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
-
# or provided.
-
#
-
# == Inheritance Examples
-
#
-
# class BankController < ActionController::Base
-
# # bank.html.erb exists
-
#
-
# class ExchangeController < BankController
-
# # exchange.html.erb exists
-
#
-
# class CurrencyController < BankController
-
#
-
# class InformationController < BankController
-
# layout "information"
-
#
-
# class TellerController < InformationController
-
# # teller.html.erb exists
-
#
-
# class EmployeeController < InformationController
-
# # employee.html.erb exists
-
# layout nil
-
#
-
# class VaultController < BankController
-
# layout :access_level_layout
-
#
-
# class TillController < BankController
-
# layout false
-
#
-
# In these examples, we have three implicit lookup scenarios:
-
# * The +BankController+ uses the "bank" layout.
-
# * The +ExchangeController+ uses the "exchange" layout.
-
# * The +CurrencyController+ inherits the layout from BankController.
-
#
-
# However, when a layout is explicitly set, the explicitly set layout wins:
-
# * The +InformationController+ uses the "information" layout, explicitly set.
-
# * The +TellerController+ also uses the "information" layout, because the parent explicitly set it.
-
# * The +EmployeeController+ uses the "employee" layout, because it set the layout to +nil+, resetting the parent configuration.
-
# * The +VaultController+ chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
-
# * The +TillController+ does not use a layout at all.
-
#
-
# == Types of layouts
-
#
-
# Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
-
# you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
-
# be done either by specifying a method reference as a symbol or using an inline method (as a proc).
-
#
-
# The method reference is the preferred approach to variable layouts and is used like this:
-
#
-
# class WeblogController < ActionController::Base
-
# layout :writers_and_readers
-
#
-
# def index
-
# # fetching posts
-
# end
-
#
-
# private
-
# def writers_and_readers
-
# logged_in? ? "writer_layout" : "reader_layout"
-
# end
-
# end
-
#
-
# Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
-
# is logged in or not.
-
#
-
# If you want to use an inline method, such as a proc, do something like this:
-
#
-
# class WeblogController < ActionController::Base
-
# layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
-
# end
-
#
-
# If an argument isn't given to the proc, it's evaluated in the context of
-
# the current controller anyway.
-
#
-
# class WeblogController < ActionController::Base
-
# layout proc { logged_in? ? "writer_layout" : "reader_layout" }
-
# end
-
#
-
# Of course, the most common way of specifying a layout is still just as a plain template name:
-
#
-
# class WeblogController < ActionController::Base
-
# layout "weblog_standard"
-
# end
-
#
-
# The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point
-
# <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>.
-
#
-
# Setting the layout to +nil+ forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
-
# Setting it to +nil+ is useful to re-enable template lookup overriding a previous configuration set in the parent:
-
#
-
# class ApplicationController < ActionController::Base
-
# layout "application"
-
# end
-
#
-
# class PostsController < ApplicationController
-
# # Will use "application" layout
-
# end
-
#
-
# class CommentsController < ApplicationController
-
# # Will search for "comments" layout and fallback "application" layout
-
# layout nil
-
# end
-
#
-
# == Conditional layouts
-
#
-
# If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
-
# a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
-
# <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
-
#
-
# class WeblogController < ActionController::Base
-
# layout "weblog_standard", except: :rss
-
#
-
# # ...
-
#
-
# end
-
#
-
# This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will
-
# be rendered directly, without wrapping a layout around the rendered view.
-
#
-
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
-
# #<tt>except: [ :rss, :text_only ]</tt> is valid, as is <tt>except: :rss</tt>.
-
#
-
# == Using a different layout in the action render call
-
#
-
# If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
-
# Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
-
# You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
-
#
-
# class WeblogController < ActionController::Base
-
# layout "weblog_standard"
-
#
-
# def help
-
# render action: "help", layout: "help"
-
# end
-
# end
-
#
-
# This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead.
-
9
module Layouts
-
9
extend ActiveSupport::Concern
-
-
9
include ActionView::Rendering
-
-
9
included do
-
15
class_attribute :_layout, instance_accessor: false
-
15
class_attribute :_layout_conditions, instance_accessor: false, default: {}
-
-
15
_write_layout_method
-
end
-
-
9
delegate :_layout_conditions, to: :class
-
-
9
module ClassMethods
-
9
def inherited(klass) # :nodoc:
-
213
super
-
213
klass._write_layout_method
-
end
-
-
# This module is mixed in if layout conditions are provided. This means
-
# that if no layout conditions are used, this method is not used
-
9
module LayoutConditions # :nodoc:
-
9
private
-
# Determines whether the current action has a layout definition by
-
# checking the action name against the :only and :except conditions
-
# set by the <tt>layout</tt> method.
-
#
-
# ==== Returns
-
# * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise.
-
9
def _conditional_layout?
-
return unless super
-
-
conditions = _layout_conditions
-
-
if only = conditions[:only]
-
only.include?(action_name)
-
elsif except = conditions[:except]
-
!except.include?(action_name)
-
else
-
true
-
end
-
end
-
end
-
-
# Specify the layout to use for this class.
-
#
-
# If the specified layout is a:
-
# String:: the String is the template name
-
# Symbol:: call the method specified by the symbol
-
# Proc:: call the passed Proc
-
# false:: There is no layout
-
# true:: raise an ArgumentError
-
# nil:: Force default layout behavior with inheritance
-
#
-
# Return value of +Proc+ and +Symbol+ arguments should be +String+, +false+, +true+ or +nil+
-
# with the same meaning as described above.
-
# ==== Parameters
-
# * <tt>layout</tt> - The layout to use.
-
#
-
# ==== Options (conditions)
-
# * :only - A list of actions to apply this layout to.
-
# * :except - Apply this layout to all actions but this one.
-
9
def layout(layout, conditions = {})
-
108
include LayoutConditions unless conditions.empty?
-
-
135
conditions.each { |k, v| conditions[k] = Array(v).map(&:to_s) }
-
108
self._layout_conditions = conditions
-
-
108
self._layout = layout
-
108
_write_layout_method
-
end
-
-
# Creates a _layout method to be called by _default_layout .
-
#
-
# If a layout is not explicitly mentioned then look for a layout with the controller's name.
-
# if nothing is found then try same procedure to find super class's layout.
-
9
def _write_layout_method # :nodoc:
-
336
silence_redefinition_of_method(:_layout)
-
-
336
prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"]
-
336
default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super"
-
336
name_clause = if name
-
336
default_behavior
-
else
-
<<-RUBY
-
super
-
RUBY
-
end
-
-
336
layout_definition = \
-
case _layout
-
when String
-
87
_layout.inspect
-
when Symbol
-
18
<<-RUBY
-
#{_layout}.tap do |layout|
-
return #{default_behavior} if layout.nil?
-
unless layout.is_a?(String) || !layout
-
raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \
-
"should have returned a String, false, or nil"
-
end
-
end
-
RUBY
-
when Proc
-
18
define_method :_layout_from_proc, &_layout
-
18
private :_layout_from_proc
-
18
<<-RUBY
-
result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'})
-
return #{default_behavior} if result.nil?
-
result
-
RUBY
-
when false
-
3
nil
-
when true
-
raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
-
when nil
-
210
name_clause
-
end
-
-
336
class_eval <<-RUBY, __FILE__, __LINE__ + 1
-
# frozen_string_literal: true
-
def _layout(lookup_context, formats)
-
if _conditional_layout?
-
#{layout_definition}
-
else
-
#{name_clause}
-
end
-
end
-
private :_layout
-
RUBY
-
end
-
-
9
private
-
# If no layout is supplied, look for a template named the return
-
# value of this method.
-
#
-
# ==== Returns
-
# * <tt>String</tt> - A template name
-
9
def _implied_layout_name
-
510
controller_path
-
end
-
end
-
-
9
def _normalize_options(options) # :nodoc:
-
super
-
-
if _include_layout?(options)
-
layout = options.delete(:layout) { :default }
-
options[:layout] = _layout_for_option(layout)
-
end
-
end
-
-
9
attr_internal_writer :action_has_layout
-
-
9
def initialize(*) # :nodoc:
-
@_action_has_layout = true
-
super
-
end
-
-
# Controls whether an action should be rendered using a layout.
-
# If you want to disable any <tt>layout</tt> settings for the
-
# current action so that it is rendered without a layout then
-
# either override this method in your controller to return false
-
# for that action or set the <tt>action_has_layout</tt> attribute
-
# to false before rendering.
-
9
def action_has_layout?
-
@_action_has_layout
-
end
-
-
9
private
-
9
def _conditional_layout?
-
true
-
end
-
-
# This will be overwritten by _write_layout_method
-
9
def _layout(*); end
-
-
# Determine the layout for a given name, taking into account the name type.
-
#
-
# ==== Parameters
-
# * <tt>name</tt> - The name of the template
-
9
def _layout_for_option(name)
-
case name
-
when String then _normalize_layout(name)
-
when Proc then name
-
when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
-
when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
-
when false, nil then nil
-
else
-
raise ArgumentError,
-
"String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}"
-
end
-
end
-
-
9
def _normalize_layout(value)
-
value.is_a?(String) && !value.match?(/\blayouts/) ? "layouts/#{value}" : value
-
end
-
-
# Returns the default layout for this controller.
-
# Optionally raises an exception if the layout could not be found.
-
#
-
# ==== Parameters
-
# * <tt>formats</tt> - The formats accepted to this layout
-
# * <tt>require_layout</tt> - If set to +true+ and layout is not found,
-
# an +ArgumentError+ exception is raised (defaults to +false+)
-
#
-
# ==== Returns
-
# * <tt>template</tt> - The template object for the default layout (or +nil+)
-
9
def _default_layout(lookup_context, formats, require_layout = false)
-
begin
-
value = _layout(lookup_context, formats) if action_has_layout?
-
rescue NameError => e
-
raise e, "Could not render layout: #{e.message}"
-
end
-
-
if require_layout && action_has_layout? && !value
-
raise ArgumentError,
-
"There was no default layout for #{self.class} in #{view_paths.inspect}"
-
end
-
-
_normalize_layout(value)
-
end
-
-
9
def _include_layout?(options)
-
(options.keys & [:body, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/log_subscriber"
-
-
3
module ActionView
-
# = Action View Log Subscriber
-
#
-
# Provides functionality so that Rails can output logs from Action View.
-
3
class LogSubscriber < ActiveSupport::LogSubscriber
-
3
VIEWS_PATTERN = /^app\/views\//
-
-
3
def initialize
-
3
@root = nil
-
3
super
-
end
-
-
3
def render_template(event)
-
info do
-
message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
-
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
-
message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
-
end
-
end
-
-
3
def render_partial(event)
-
debug do
-
message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
-
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
-
message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
-
message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil?
-
message
-
end
-
end
-
-
3
def render_layout(event)
-
info do
-
message = +" Rendered layout #{from_rails_root(event.payload[:identifier])}"
-
message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
-
end
-
end
-
-
3
def render_collection(event)
-
identifier = event.payload[:identifier] || "templates"
-
-
debug do
-
message = +" Rendered collection of #{from_rails_root(identifier)}"
-
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
-
message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
-
message
-
end
-
end
-
-
3
def start(name, id, payload)
-
log_rendering_start(payload, name)
-
-
super
-
end
-
-
3
def logger
-
ActionView::Base.logger
-
end
-
-
3
private
-
3
EMPTY = ""
-
3
def from_rails_root(string) # :doc:
-
string = string.sub(rails_root, EMPTY)
-
string.sub!(VIEWS_PATTERN, EMPTY)
-
string
-
end
-
-
3
def rails_root # :doc:
-
@root ||= "#{Rails.root}/"
-
end
-
-
3
def render_count(payload) # :doc:
-
if payload[:cache_hits]
-
"[#{payload[:cache_hits]} / #{payload[:count]} cache hits]"
-
else
-
"[#{payload[:count]} times]"
-
end
-
end
-
-
3
def cache_message(payload) # :doc:
-
case payload[:cache_hit]
-
when :hit
-
"[cache hit]"
-
when :miss
-
"[cache miss]"
-
end
-
end
-
-
3
def log_rendering_start(payload, name)
-
debug do
-
qualifier =
-
if name == "render_template.action_view"
-
""
-
elsif name == "render_layout.action_view"
-
"layout "
-
end
-
-
return unless qualifier
-
-
message = +" Rendering #{qualifier}#{from_rails_root(payload[:identifier])}"
-
message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
-
message
-
end
-
end
-
end
-
end
-
-
3
ActionView::LogSubscriber.attach_to :action_view
-
# frozen_string_literal: true
-
-
3
require "concurrent/map"
-
3
require "active_support/core_ext/module/attribute_accessors"
-
3
require "action_view/template/resolver"
-
-
3
module ActionView
-
# = Action View Lookup Context
-
#
-
# <tt>LookupContext</tt> is the object responsible for holding all information
-
# required for looking up templates, i.e. view paths and details.
-
# <tt>LookupContext</tt> is also responsible for generating a key, given to
-
# view paths, used in the resolver cache lookup. Since this key is generated
-
# only once during the request, it speeds up all cache accesses.
-
3
class LookupContext #:nodoc:
-
3
attr_accessor :prefixes, :rendered_format
-
3
deprecate :rendered_format
-
3
deprecate :rendered_format=
-
-
3
mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances
-
-
3
mattr_accessor :registered_details, default: []
-
-
3
def self.register_detail(name, &block)
-
12
registered_details << name
-
12
Accessors::DEFAULT_PROCS[name] = block
-
-
12
Accessors.define_method(:"default_#{name}", &block)
-
12
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
-
def #{name}
-
@details[:#{name}] || []
-
end
-
-
def #{name}=(value)
-
value = value.present? ? Array(value) : default_#{name}
-
_set_detail(:#{name}, value) if value != @details[:#{name}]
-
end
-
METHOD
-
end
-
-
# Holds accessors for the registered details.
-
3
module Accessors #:nodoc:
-
3
DEFAULT_PROCS = {}
-
end
-
-
3
register_detail(:locale) do
-
locales = [I18n.locale]
-
locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
-
locales << I18n.default_locale
-
locales.uniq!
-
locales
-
end
-
3
register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
-
3
register_detail(:variants) { [] }
-
3
register_detail(:handlers) { Template::Handlers.extensions }
-
-
3
class DetailsKey #:nodoc:
-
3
alias :eql? :equal?
-
-
3
@details_keys = Concurrent::Map.new
-
3
@digest_cache = Concurrent::Map.new
-
3
@view_context_mutex = Mutex.new
-
-
3
def self.digest_cache(details)
-
@digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
-
end
-
-
3
def self.details_cache_key(details)
-
if details[:formats]
-
details = details.dup
-
details[:formats] &= Template::Types.symbols
-
end
-
@details_keys[details] ||= Object.new
-
end
-
-
3
def self.clear
-
3
ActionView::ViewPaths.all_view_paths.each do |path_set|
-
3
path_set.each(&:clear_cache)
-
end
-
3
ActionView::LookupContext.fallbacks.each(&:clear_cache)
-
3
@view_context_class = nil
-
3
@details_keys.clear
-
3
@digest_cache.clear
-
end
-
-
3
def self.digest_caches
-
@digest_cache.values
-
end
-
-
3
def self.view_context_class(klass)
-
@view_context_mutex.synchronize do
-
@view_context_class ||= klass.with_empty_template_cache
-
end
-
end
-
end
-
-
# Add caching behavior on top of Details.
-
3
module DetailsCache
-
3
attr_accessor :cache
-
-
# Calculate the details key. Remove the handlers from calculation to improve performance
-
# since the user cannot modify it explicitly.
-
3
def details_key #:nodoc:
-
@details_key ||= DetailsKey.details_cache_key(@details) if @cache
-
end
-
-
# Temporary skip passing the details_key forward.
-
3
def disable_cache
-
old_value, @cache = @cache, false
-
yield
-
ensure
-
@cache = old_value
-
end
-
-
3
private
-
3
def _set_detail(key, value) # :doc:
-
@details = @details.dup if @digest_cache || @details_key
-
@digest_cache = nil
-
@details_key = nil
-
@details[key] = value
-
end
-
end
-
-
# Helpers related to template lookup using the lookup context information.
-
3
module ViewPaths
-
3
attr_reader :view_paths, :html_fallback_for_js
-
-
3
def find(name, prefixes = [], partial = false, keys = [], options = {})
-
@view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
-
end
-
3
alias :find_template :find
-
-
3
alias :find_file :find
-
3
deprecate :find_file
-
-
3
def find_all(name, prefixes = [], partial = false, keys = [], options = {})
-
@view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
-
end
-
-
3
def exists?(name, prefixes = [], partial = false, keys = [], **options)
-
@view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
-
end
-
3
alias :template_exists? :exists?
-
-
3
def any?(name, prefixes = [], partial = false)
-
@view_paths.exists?(*args_for_any(name, prefixes, partial))
-
end
-
3
alias :any_templates? :any?
-
-
# Adds fallbacks to the view paths. Useful in cases when you are rendering
-
# a :file.
-
3
def with_fallbacks
-
view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
-
-
if block_given?
-
ActiveSupport::Deprecation.warn <<~eowarn.squish
-
Calling `with_fallbacks` with a block is deprecated. Call methods on
-
the lookup context returned by `with_fallbacks` instead.
-
eowarn
-
-
begin
-
_view_paths = @view_paths
-
@view_paths = view_paths
-
yield
-
ensure
-
@view_paths = _view_paths
-
end
-
else
-
ActionView::LookupContext.new(view_paths, @details, @prefixes)
-
end
-
end
-
-
3
private
-
# Whenever setting view paths, makes a copy so that we can manipulate them in
-
# instance objects as we wish.
-
3
def build_view_paths(paths)
-
ActionView::PathSet.new(Array(paths))
-
end
-
-
3
def args_for_lookup(name, prefixes, partial, keys, details_options)
-
name, prefixes = normalize_name(name, prefixes)
-
details, details_key = detail_args_for(details_options)
-
[name, prefixes, partial || false, details, details_key, keys]
-
end
-
-
# Compute details hash and key according to user options (e.g. passed from #render).
-
3
def detail_args_for(options) # :doc:
-
return @details, details_key if options.empty? # most common path.
-
user_details = @details.merge(options)
-
-
if @cache
-
details_key = DetailsKey.details_cache_key(user_details)
-
else
-
details_key = nil
-
end
-
-
[user_details, details_key]
-
end
-
-
3
def args_for_any(name, prefixes, partial)
-
name, prefixes = normalize_name(name, prefixes)
-
details, details_key = detail_args_for_any
-
[name, prefixes, partial || false, details, details_key]
-
end
-
-
3
def detail_args_for_any
-
@detail_args_for_any ||= begin
-
details = {}
-
-
registered_details.each do |k|
-
if k == :variants
-
details[k] = :any
-
else
-
details[k] = Accessors::DEFAULT_PROCS[k].call
-
end
-
end
-
-
if @cache
-
[details, DetailsKey.details_cache_key(details)]
-
else
-
[details, nil]
-
end
-
end
-
end
-
-
# Support legacy foo.erb names even though we now ignore .erb
-
# as well as incorrectly putting part of the path in the template
-
# name instead of the prefix.
-
3
def normalize_name(name, prefixes)
-
prefixes = prefixes.presence
-
parts = name.to_s.split("/")
-
parts.shift if parts.first.empty?
-
name = parts.pop
-
-
return name, prefixes || [""] if parts.empty?
-
-
parts = parts.join("/")
-
prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
-
-
return name, prefixes
-
end
-
end
-
-
3
include Accessors
-
3
include DetailsCache
-
3
include ViewPaths
-
-
3
def initialize(view_paths, details = {}, prefixes = [])
-
@details_key = nil
-
@digest_cache = nil
-
@cache = true
-
@prefixes = prefixes
-
-
@details = initialize_details({}, details)
-
@view_paths = build_view_paths(view_paths)
-
end
-
-
3
def digest_cache
-
@digest_cache ||= DetailsKey.digest_cache(@details)
-
end
-
-
3
def with_prepended_formats(formats)
-
details = @details.dup
-
details[:formats] = formats
-
-
self.class.new(@view_paths, details, @prefixes)
-
end
-
-
3
def initialize_details(target, details)
-
registered_details.each do |k|
-
target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call
-
end
-
target
-
end
-
3
private :initialize_details
-
-
# Override formats= to expand ["*/*"] values and automatically
-
# add :html as fallback to :js.
-
3
def formats=(values)
-
if values
-
values = values.dup
-
values.concat(default_formats) if values.delete "*/*"
-
values.uniq!
-
-
invalid_values = (values - Template::Types.symbols)
-
unless invalid_values.empty?
-
raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
-
end
-
-
if values == [:js]
-
values << :html
-
@html_fallback_for_js = true
-
end
-
end
-
super(values)
-
end
-
-
# Override locale to return a symbol instead of array.
-
3
def locale
-
@details[:locale].first
-
end
-
-
# Overload locale= to also set the I18n.locale. If the current I18n.config object responds
-
# to original_config, it means that it has a copy of the original I18n configuration and it's
-
# acting as proxy, which we need to skip.
-
3
def locale=(value)
-
if value
-
config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
-
config.locale = value
-
end
-
-
super(default_locale)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
9
module ModelNaming #:nodoc:
-
# Converts the given object to an ActiveModel compliant one.
-
9
def convert_to_model(object)
-
object.respond_to?(:to_model) ? object.to_model : object
-
end
-
-
9
def model_name_from_record_or_class(record_or_class)
-
convert_to_model(record_or_class).model_name
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView #:nodoc:
-
# = Action View PathSet
-
#
-
# This class is used to store and access paths in Action View. A number of
-
# operations are defined so that you can search among the paths in this
-
# set and also perform operations on other +PathSet+ objects.
-
#
-
# A +LookupContext+ will use a +PathSet+ to store the paths in its context.
-
9
class PathSet #:nodoc:
-
9
include Enumerable
-
-
9
attr_reader :paths
-
-
9
delegate :[], :include?, :pop, :size, :each, to: :paths
-
-
9
def initialize(paths = [])
-
60
@paths = typecast paths
-
end
-
-
9
def initialize_copy(other)
-
3
@paths = other.paths.dup
-
3
self
-
end
-
-
9
def to_ary
-
6
paths.dup
-
end
-
-
9
def compact
-
PathSet.new paths.compact
-
end
-
-
9
def +(array)
-
6
PathSet.new(paths + array)
-
end
-
-
9
%w(<< concat push insert unshift).each do |method|
-
45
class_eval <<-METHOD, __FILE__, __LINE__ + 1
-
def #{method}(*args)
-
paths.#{method}(*typecast(args))
-
end
-
METHOD
-
end
-
-
9
def find(*args)
-
find_all(*args).first || raise(MissingTemplate.new(self, *args))
-
end
-
-
9
alias :find_file :find
-
9
deprecate :find_file
-
-
9
def find_all(path, prefixes = [], *args)
-
_find_all path, prefixes, args
-
end
-
-
9
def exists?(path, prefixes, *args)
-
find_all(path, prefixes, *args).any?
-
end
-
-
9
def find_all_with_query(query) # :nodoc:
-
paths.each do |resolver|
-
templates = resolver.find_all_with_query(query)
-
return templates unless templates.empty?
-
end
-
-
[]
-
end
-
-
9
private
-
9
def _find_all(path, prefixes, args)
-
prefixes = [prefixes] if String === prefixes
-
prefixes.each do |prefix|
-
paths.each do |resolver|
-
templates = resolver.find_all(path, prefix, *args)
-
return templates unless templates.empty?
-
end
-
end
-
[]
-
end
-
-
9
def typecast(paths)
-
60
paths.map do |path|
-
33
case path
-
when Pathname, String
-
21
OptimizedFileSystemResolver.new path.to_s
-
else
-
12
path
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "action_view"
-
require "rails"
-
-
module ActionView
-
# = Action View Railtie
-
class Railtie < Rails::Engine # :nodoc:
-
NULL_OPTION = Object.new
-
-
config.action_view = ActiveSupport::OrderedOptions.new
-
config.action_view.embed_authenticity_token_in_remote_forms = nil
-
config.action_view.debug_missing_translation = true
-
config.action_view.default_enforce_utf8 = nil
-
config.action_view.finalize_compiled_template_methods = NULL_OPTION
-
-
config.eager_load_namespaces << ActionView
-
-
config.after_initialize do |app|
-
ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms =
-
app.config.action_view.delete(:embed_authenticity_token_in_remote_forms)
-
end
-
-
config.after_initialize do |app|
-
form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms)
-
ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms
-
end
-
-
config.after_initialize do |app|
-
form_with_generates_ids = app.config.action_view.delete(:form_with_generates_ids)
-
unless form_with_generates_ids.nil?
-
ActionView::Helpers::FormHelper.form_with_generates_ids = form_with_generates_ids
-
end
-
end
-
-
config.after_initialize do |app|
-
default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8)
-
unless default_enforce_utf8.nil?
-
ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
-
end
-
end
-
-
config.after_initialize do |app|
-
ActiveSupport.on_load(:action_view) do
-
app.config.action_view.each do |k, v|
-
if k == :raise_on_missing_translations
-
ActiveSupport::Deprecation.warn \
-
"action_view.raise_on_missing_translations is deprecated and will be removed in Rails 6.2. " \
-
"Set i18n.raise_on_missing_translations instead. " \
-
"Note that this new setting also affects how missing translations are handled in controllers."
-
end
-
send "#{k}=", v
-
end
-
end
-
end
-
-
initializer "action_view.finalize_compiled_template_methods" do |app|
-
ActiveSupport.on_load(:action_view) do
-
option = app.config.action_view.delete(:finalize_compiled_template_methods)
-
-
if option != NULL_OPTION
-
ActiveSupport::Deprecation.warn "action_view.finalize_compiled_template_methods is deprecated and has no effect"
-
end
-
end
-
end
-
-
initializer "action_view.logger" do
-
ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
-
end
-
-
initializer "action_view.caching" do |app|
-
ActiveSupport.on_load(:action_view) do
-
if app.config.action_view.cache_template_loading.nil?
-
ActionView::Resolver.caching = app.config.cache_classes
-
end
-
end
-
end
-
-
initializer "action_view.setup_action_pack" do |app|
-
ActiveSupport.on_load(:action_controller) do
-
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
-
end
-
end
-
-
initializer "action_view.collection_caching", after: "action_controller.set_configs" do |app|
-
PartialRenderer.collection_cache = app.config.action_controller.cache_store
-
end
-
-
config.after_initialize do |app|
-
enable_caching = if app.config.action_view.cache_template_loading.nil?
-
app.config.cache_classes
-
else
-
app.config.action_view.cache_template_loading
-
end
-
-
unless enable_caching
-
app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
-
end
-
end
-
-
rake_tasks do |app|
-
unless app.config.api_only
-
load "action_view/tasks/cache_digests.rake"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "active_support/core_ext/module"
-
9
require "action_view/model_naming"
-
-
9
module ActionView
-
# RecordIdentifier encapsulates methods used by various ActionView helpers
-
# to associate records with DOM elements.
-
#
-
# Consider for example the following code that form of post:
-
#
-
# <%= form_for(post) do |f| %>
-
# <%= f.text_field :body %>
-
# <% end %>
-
#
-
# When +post+ is a new, unsaved ActiveRecord::Base instance, the resulting HTML
-
# is:
-
#
-
# <form class="new_post" id="new_post" action="/posts" accept-charset="UTF-8" method="post">
-
# <input type="text" name="post[body]" id="post_body" />
-
# </form>
-
#
-
# When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML
-
# is:
-
#
-
# <form class="edit_post" id="edit_post_42" action="/posts/42" accept-charset="UTF-8" method="post">
-
# <input type="text" value="What a wonderful world!" name="post[body]" id="post_body" />
-
# </form>
-
#
-
# In both cases, the +id+ and +class+ of the wrapping DOM element are
-
# automatically generated, following naming conventions encapsulated by the
-
# RecordIdentifier methods #dom_id and #dom_class:
-
#
-
# dom_id(Post.new) # => "new_post"
-
# dom_class(Post.new) # => "post"
-
# dom_id(Post.find 42) # => "post_42"
-
# dom_class(Post.find 42) # => "post"
-
#
-
# Note that these methods do not strictly require +Post+ to be a subclass of
-
# ActiveRecord::Base.
-
# Any +Post+ class will work as long as its instances respond to +to_key+
-
# and +model_name+, given that +model_name+ responds to +param_key+.
-
# For instance:
-
#
-
# class Post
-
# attr_accessor :to_key
-
#
-
# def model_name
-
# OpenStruct.new param_key: 'post'
-
# end
-
#
-
# def self.find(id)
-
# new.tap { |post| post.to_key = [id] }
-
# end
-
# end
-
9
module RecordIdentifier
-
9
extend self
-
9
extend ModelNaming
-
-
9
include ModelNaming
-
-
9
JOIN = "_"
-
9
NEW = "new"
-
-
# The DOM class convention is to use the singular form of an object or class.
-
#
-
# dom_class(post) # => "post"
-
# dom_class(Person) # => "person"
-
#
-
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
-
#
-
# dom_class(post, :edit) # => "edit_post"
-
# dom_class(Person, :edit) # => "edit_person"
-
9
def dom_class(record_or_class, prefix = nil)
-
singular = model_name_from_record_or_class(record_or_class).param_key
-
prefix ? "#{prefix}#{JOIN}#{singular}" : singular
-
end
-
-
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
-
# If no id is found, prefix with "new_" instead.
-
#
-
# dom_id(Post.find(45)) # => "post_45"
-
# dom_id(Post.new) # => "new_post"
-
#
-
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
-
#
-
# dom_id(Post.find(45), :edit) # => "edit_post_45"
-
# dom_id(Post.new, :custom) # => "custom_post"
-
9
def dom_id(record, prefix = nil)
-
if record_id = record_key_for_dom_id(record)
-
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
-
else
-
dom_class(record, prefix || NEW)
-
end
-
end
-
-
9
private
-
# Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
-
# This can be overwritten to customize the default generated string representation if desired.
-
# If you need to read back a key from a dom_id in order to query for the underlying database record,
-
# you should write a helper like 'person_record_from_dom_id' that will extract the key either based
-
# on the default implementation (which just joins all key attributes with '_') or on your own
-
# overwritten version of the method. By default, this implementation passes the key string through a
-
# method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
-
# make sure yourself that your dom ids are valid, in case you overwrite this method.
-
9
def record_key_for_dom_id(record) # :doc:
-
key = convert_to_model(record).to_key
-
key ? key.join(JOIN) : key
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "concurrent/map"
-
-
3
module ActionView
-
# This class defines the interface for a renderer. Each class that
-
# subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
-
# render a specific type of object.
-
#
-
# The base +Renderer+ class uses its +render+ method to delegate to the
-
# renderers. These currently consist of
-
#
-
# PartialRenderer - Used for rendering partials
-
# TemplateRenderer - Used for rendering other types of templates
-
# StreamingTemplateRenderer - Used for streaming
-
#
-
# Whenever the +render+ method is called on the base +Renderer+ class, a new
-
# renderer object of the correct type is created, and the +render+ method on
-
# that new object is called in turn. This abstracts the set up and rendering
-
# into a separate classes for partials and templates.
-
3
class AbstractRenderer #:nodoc:
-
3
delegate :template_exists?, :any_templates?, :formats, to: :@lookup_context
-
-
3
def initialize(lookup_context)
-
@lookup_context = lookup_context
-
end
-
-
3
def render
-
raise NotImplementedError
-
end
-
-
3
module ObjectRendering # :nodoc:
-
3
PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k|
-
h[k] = Concurrent::Map.new
-
end
-
-
3
def initialize(lookup_context, options)
-
super
-
@context_prefix = lookup_context.prefixes.first
-
end
-
-
3
private
-
3
def local_variable(path)
-
if as = @options[:as]
-
raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
-
as.to_sym
-
else
-
begin
-
base = path.end_with?("/") ? "" : File.basename(path)
-
raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
-
$1.to_sym
-
end
-
end
-
end
-
-
3
IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
-
"make sure your partial name starts with underscore."
-
-
3
OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
-
"make sure it starts with lowercase letter, " \
-
"and is followed by any combination of letters, numbers and underscores."
-
-
3
def raise_invalid_identifier(path)
-
raise ArgumentError, IDENTIFIER_ERROR_MESSAGE % path
-
end
-
-
3
def raise_invalid_option_as(as)
-
raise ArgumentError, OPTION_AS_ERROR_MESSAGE % as
-
end
-
-
# Obtains the path to where the object's partial is located. If the object
-
# responds to +to_partial_path+, then +to_partial_path+ will be called and
-
# will provide the path. If the object does not respond to +to_partial_path+,
-
# then an +ArgumentError+ is raised.
-
#
-
# If +prefix_partial_path_with_controller_namespace+ is true, then this
-
# method will prefix the partial paths with a namespace.
-
3
def partial_path(object, view)
-
object = object.to_model if object.respond_to?(:to_model)
-
-
path = if object.respond_to?(:to_partial_path)
-
object.to_partial_path
-
else
-
raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
-
end
-
-
if view.prefix_partial_path_with_controller_namespace
-
PREFIXED_PARTIAL_NAMES[@context_prefix][path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
-
else
-
path
-
end
-
end
-
-
3
def merge_prefix_into_object_path(prefix, object_path)
-
if prefix.include?(?/) && object_path.include?(?/)
-
prefixes = []
-
prefix_array = File.dirname(prefix).split("/")
-
object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
-
-
prefix_array.each_with_index do |dir, index|
-
break if dir == object_path_array[index]
-
prefixes << dir
-
end
-
-
(prefixes << object_path).join("/")
-
else
-
object_path
-
end
-
end
-
end
-
-
3
class RenderedCollection # :nodoc:
-
3
def self.empty(format)
-
EmptyCollection.new format
-
end
-
-
3
attr_reader :rendered_templates
-
-
3
def initialize(rendered_templates, spacer)
-
@rendered_templates = rendered_templates
-
@spacer = spacer
-
end
-
-
3
def body
-
@rendered_templates.map(&:body).join(@spacer.body).html_safe
-
end
-
-
3
def format
-
rendered_templates.first.format
-
end
-
-
3
class EmptyCollection
-
3
attr_reader :format
-
-
3
def initialize(format)
-
@format = format
-
end
-
-
3
def body; nil; end
-
end
-
end
-
-
3
class RenderedTemplate # :nodoc:
-
3
attr_reader :body, :template
-
-
3
def initialize(body, template)
-
@body = body
-
@template = template
-
end
-
-
3
def format
-
template.format
-
end
-
-
3
EMPTY_SPACER = Struct.new(:body).new
-
end
-
-
3
private
-
3
NO_DETAILS = {}.freeze
-
-
3
def extract_details(options) # :doc:
-
details = nil
-
@lookup_context.registered_details.each do |key|
-
value = options[key]
-
-
if value
-
(details ||= {})[key] = Array(value)
-
end
-
end
-
details || NO_DETAILS
-
end
-
-
3
def prepend_formats(formats) # :doc:
-
formats = Array(formats)
-
return if formats.empty? || @lookup_context.html_fallback_for_js
-
-
@lookup_context.formats = formats | @lookup_context.formats
-
end
-
-
3
def build_rendered_template(content, template)
-
RenderedTemplate.new content, template
-
end
-
-
3
def build_rendered_collection(templates, spacer)
-
RenderedCollection.new templates, spacer
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "action_view/renderer/partial_renderer"
-
-
3
module ActionView
-
3
class PartialIteration
-
# The number of iterations that will be done by the partial.
-
3
attr_reader :size
-
-
# The current iteration of the partial.
-
3
attr_reader :index
-
-
3
def initialize(size)
-
@size = size
-
@index = 0
-
end
-
-
# Check if this is the first iteration of the partial.
-
3
def first?
-
index == 0
-
end
-
-
# Check if this is the last iteration of the partial.
-
3
def last?
-
index == size - 1
-
end
-
-
3
def iterate! # :nodoc:
-
@index += 1
-
end
-
end
-
-
3
class CollectionRenderer < PartialRenderer # :nodoc:
-
3
include ObjectRendering
-
-
3
class CollectionIterator # :nodoc:
-
3
include Enumerable
-
-
3
def initialize(collection)
-
@collection = collection
-
end
-
-
3
def each(&blk)
-
@collection.each(&blk)
-
end
-
-
3
def size
-
@collection.size
-
end
-
end
-
-
3
class SameCollectionIterator < CollectionIterator # :nodoc:
-
3
def initialize(collection, path, variables)
-
super(collection)
-
@path = path
-
@variables = variables
-
end
-
-
3
def from_collection(collection)
-
self.class.new(collection, @path, @variables)
-
end
-
-
3
def each_with_info
-
return enum_for(:each_with_info) unless block_given?
-
variables = [@path] + @variables
-
@collection.each { |o| yield(o, variables) }
-
end
-
end
-
-
3
class PreloadCollectionIterator < SameCollectionIterator # :nodoc:
-
3
def initialize(collection, path, variables, relation)
-
super(collection, path, variables)
-
relation.skip_preloading! unless relation.loaded?
-
@relation = relation
-
end
-
-
3
def from_collection(collection)
-
self.class.new(collection, @path, @variables, @relation)
-
end
-
-
3
def each_with_info
-
return super unless block_given?
-
@relation.preload_associations(@collection)
-
super
-
end
-
end
-
-
3
class MixedCollectionIterator < CollectionIterator # :nodoc:
-
3
def initialize(collection, paths)
-
super(collection)
-
@paths = paths
-
end
-
-
3
def each_with_info
-
return enum_for(:each_with_info) unless block_given?
-
@collection.each_with_index { |o, i| yield(o, @paths[i]) }
-
end
-
end
-
-
3
def render_collection_with_partial(collection, partial, context, block)
-
iter_vars = retrieve_variable(partial)
-
-
collection = if collection.respond_to?(:preload_associations)
-
PreloadCollectionIterator.new(collection, partial, iter_vars, collection)
-
else
-
SameCollectionIterator.new(collection, partial, iter_vars)
-
end
-
-
-
template = find_template(partial, @locals.keys + iter_vars)
-
-
layout = if !block && (layout = @options[:layout])
-
find_template(layout.to_s, @locals.keys + iter_vars)
-
end
-
-
render_collection(collection, context, partial, template, layout, block)
-
end
-
-
3
def render_collection_derive_partial(collection, context, block)
-
paths = collection.map { |o| partial_path(o, context) }
-
-
if paths.uniq.length == 1
-
# Homogeneous
-
render_collection_with_partial(collection, paths.first, context, block)
-
else
-
if @options[:cached]
-
raise NotImplementedError, "render caching requires a template. Please specify a partial when rendering"
-
end
-
-
paths.map! { |path| retrieve_variable(path).unshift(path) }
-
collection = MixedCollectionIterator.new(collection, paths)
-
render_collection(collection, context, nil, nil, nil, block)
-
end
-
end
-
-
3
private
-
3
def retrieve_variable(path)
-
variable = local_variable(path)
-
[variable, :"#{variable}_counter", :"#{variable}_iteration"]
-
end
-
-
3
def render_collection(collection, view, path, template, layout, block)
-
identifier = (template && template.identifier) || path
-
ActiveSupport::Notifications.instrument(
-
"render_collection.action_view",
-
identifier: identifier,
-
layout: layout && layout.virtual_path,
-
count: collection.size
-
) do |payload|
-
spacer = if @options.key?(:spacer_template)
-
spacer_template = find_template(@options[:spacer_template], @locals.keys)
-
build_rendered_template(spacer_template.render(view, @locals), spacer_template)
-
else
-
RenderedTemplate::EMPTY_SPACER
-
end
-
-
collection_body = if template
-
cache_collection_render(payload, view, template, collection) do |filtered_collection|
-
collection_with_template(view, template, layout, filtered_collection)
-
end
-
else
-
collection_with_template(view, nil, layout, collection)
-
end
-
-
return RenderedCollection.empty(@lookup_context.formats.first) if collection_body.empty?
-
-
build_rendered_collection(collection_body, spacer)
-
end
-
end
-
-
3
def collection_with_template(view, template, layout, collection)
-
locals = @locals
-
cache = {}
-
-
partial_iteration = PartialIteration.new(collection.size)
-
-
collection.each_with_info.map do |object, (path, as, counter, iteration)|
-
index = partial_iteration.index
-
-
locals[as] = object
-
locals[counter] = index
-
locals[iteration] = partial_iteration
-
-
_template = (cache[path] ||= (template || find_template(path, @locals.keys + [as, counter, iteration])))
-
-
content = _template.render(view, locals)
-
content = layout.render(view, locals) { content } if layout
-
partial_iteration.iterate!
-
build_rendered_template(content, _template)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
class ObjectRenderer < PartialRenderer # :nodoc:
-
include ObjectRendering
-
-
def initialize(lookup_context, options)
-
super
-
@object = nil
-
@local_name = nil
-
end
-
-
def render_object_with_partial(object, partial, context, block)
-
@object = object
-
@local_name = local_variable(partial)
-
render(partial, context, block)
-
end
-
-
def render_object_derive_partial(object, context, block)
-
path = partial_path(object, context)
-
render_object_with_partial(object, path, context, block)
-
end
-
-
private
-
def template_keys(path)
-
super + [@local_name]
-
end
-
-
def render_partial_template(view, locals, template, layout, block)
-
locals[@local_name || template.variable] = @object
-
super(view, locals, template, layout, block)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "action_view/renderer/partial_renderer/collection_caching"
-
-
3
module ActionView
-
# = Action View Partials
-
#
-
# There's also a convenience method for rendering sub templates within the current controller that depends on a
-
# single object (we call this kind of sub templates for partials). It relies on the fact that partials should
-
# follow the naming convention of being prefixed with an underscore -- as to separate them from regular
-
# templates that could be rendered on their own.
-
#
-
# In a template for Advertiser#account:
-
#
-
# <%= render partial: "account" %>
-
#
-
# This would render "advertiser/_account.html.erb".
-
#
-
# In another template for Advertiser#buy, we could have:
-
#
-
# <%= render partial: "account", locals: { account: @buyer } %>
-
#
-
# <% @advertisements.each do |ad| %>
-
# <%= render partial: "ad", locals: { ad: ad } %>
-
# <% end %>
-
#
-
# This would first render <tt>advertiser/_account.html.erb</tt> with <tt>@buyer</tt> passed in as the local variable +account+, then
-
# render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display.
-
#
-
# == The :as and :object options
-
#
-
# By default ActionView::PartialRenderer doesn't have any local variables.
-
# The <tt>:object</tt> option can be used to pass an object to the partial. For instance:
-
#
-
# <%= render partial: "account", object: @buyer %>
-
#
-
# would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is
-
# equivalent to:
-
#
-
# <%= render partial: "account", locals: { account: @buyer } %>
-
#
-
# With the <tt>:as</tt> option we can specify a different name for said local variable. For example, if we
-
# wanted it to be +user+ instead of +account+ we'd do:
-
#
-
# <%= render partial: "account", object: @buyer, as: 'user' %>
-
#
-
# This is equivalent to
-
#
-
# <%= render partial: "account", locals: { user: @buyer } %>
-
#
-
# == \Rendering a collection of partials
-
#
-
# The example of partial use describes a familiar pattern where a template needs to iterate over an array and
-
# render a sub template for each of the elements. This pattern has been implemented as a single method that
-
# accepts an array and renders a partial by the same name as the elements contained within. So the three-lined
-
# example in "Using partials" can be rewritten with a single line:
-
#
-
# <%= render partial: "ad", collection: @advertisements %>
-
#
-
# This will render <tt>advertiser/_ad.html.erb</tt> and pass the local variable +ad+ to the template for display. An
-
# iteration object will automatically be made available to the template with a name of the form
-
# +partial_name_iteration+. The iteration object has knowledge about which index the current object has in
-
# the collection and the total size of the collection. The iteration object also has two convenience methods,
-
# +first?+ and +last?+. In the case of the example above, the template would be fed +ad_iteration+.
-
# For backwards compatibility the +partial_name_counter+ is still present and is mapped to the iteration's
-
# +index+ method.
-
#
-
# The <tt>:as</tt> option may be used when rendering partials.
-
#
-
# You can specify a partial to be rendered between elements via the <tt>:spacer_template</tt> option.
-
# The following example will render <tt>advertiser/_ad_divider.html.erb</tt> between each ad partial:
-
#
-
# <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
-
#
-
# If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return +nil+. This will allow you
-
# to specify a text which will be displayed instead by using this form:
-
#
-
# <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
-
#
-
# == \Rendering shared partials
-
#
-
# Two controllers can share a set of partials and render them like this:
-
#
-
# <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %>
-
#
-
# This will render the partial <tt>advertisement/_ad.html.erb</tt> regardless of which controller this is being called from.
-
#
-
# == \Rendering objects that respond to +to_partial_path+
-
#
-
# Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
-
# and pick the proper path by checking +to_partial_path+ method.
-
#
-
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
-
# # <%= render partial: "accounts/account", locals: { account: @account} %>
-
# <%= render partial: @account %>
-
#
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
-
# # that's why we can replace:
-
# # <%= render partial: "posts/post", collection: @posts %>
-
# <%= render partial: @posts %>
-
#
-
# == \Rendering the default case
-
#
-
# If you're not going to be using any of the options like collections or layouts, you can also use the short-hand
-
# defaults of render to render partials. Examples:
-
#
-
# # Instead of <%= render partial: "account" %>
-
# <%= render "account" %>
-
#
-
# # Instead of <%= render partial: "account", locals: { account: @buyer } %>
-
# <%= render "account", account: @buyer %>
-
#
-
# # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
-
# # <%= render partial: "accounts/account", locals: { account: @account} %>
-
# <%= render @account %>
-
#
-
# # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+,
-
# # that's why we can replace:
-
# # <%= render partial: "posts/post", collection: @posts %>
-
# <%= render @posts %>
-
#
-
# == \Rendering partials with layouts
-
#
-
# Partials can have their own layouts applied to them. These layouts are different than the ones that are
-
# specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types
-
# of users:
-
#
-
# <%# app/views/users/index.html.erb %>
-
# Here's the administrator:
-
# <%= render partial: "user", layout: "administrator", locals: { user: administrator } %>
-
#
-
# Here's the editor:
-
# <%= render partial: "user", layout: "editor", locals: { user: editor } %>
-
#
-
# <%# app/views/users/_user.html.erb %>
-
# Name: <%= user.name %>
-
#
-
# <%# app/views/users/_administrator.html.erb %>
-
# <div id="administrator">
-
# Budget: $<%= user.budget %>
-
# <%= yield %>
-
# </div>
-
#
-
# <%# app/views/users/_editor.html.erb %>
-
# <div id="editor">
-
# Deadline: <%= user.deadline %>
-
# <%= yield %>
-
# </div>
-
#
-
# ...this will return:
-
#
-
# Here's the administrator:
-
# <div id="administrator">
-
# Budget: $<%= user.budget %>
-
# Name: <%= user.name %>
-
# </div>
-
#
-
# Here's the editor:
-
# <div id="editor">
-
# Deadline: <%= user.deadline %>
-
# Name: <%= user.name %>
-
# </div>
-
#
-
# If a collection is given, the layout will be rendered once for each item in
-
# the collection. For example, these two snippets have the same output:
-
#
-
# <%# app/views/users/_user.html.erb %>
-
# Name: <%= user.name %>
-
#
-
# <%# app/views/users/index.html.erb %>
-
# <%# This does not use layouts %>
-
# <ul>
-
# <% users.each do |user| -%>
-
# <li>
-
# <%= render partial: "user", locals: { user: user } %>
-
# </li>
-
# <% end -%>
-
# </ul>
-
#
-
# <%# app/views/users/_li_layout.html.erb %>
-
# <li>
-
# <%= yield %>
-
# </li>
-
#
-
# <%# app/views/users/index.html.erb %>
-
# <ul>
-
# <%= render partial: "user", layout: "li_layout", collection: users %>
-
# </ul>
-
#
-
# Given two users whose names are Alice and Bob, these snippets return:
-
#
-
# <ul>
-
# <li>
-
# Name: Alice
-
# </li>
-
# <li>
-
# Name: Bob
-
# </li>
-
# </ul>
-
#
-
# The current object being rendered, as well as the object_counter, will be
-
# available as local variables inside the layout template under the same names
-
# as available in the partial.
-
#
-
# You can also apply a layout to a block within any template:
-
#
-
# <%# app/views/users/_chief.html.erb %>
-
# <%= render(layout: "administrator", locals: { user: chief }) do %>
-
# Title: <%= chief.title %>
-
# <% end %>
-
#
-
# ...this will return:
-
#
-
# <div id="administrator">
-
# Budget: $<%= user.budget %>
-
# Title: <%= chief.name %>
-
# </div>
-
#
-
# As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout.
-
#
-
# If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass
-
# an array to layout and treat it as an enumerable.
-
#
-
# <%# app/views/users/_user.html.erb %>
-
# <div class="user">
-
# Budget: $<%= user.budget %>
-
# <%= yield user %>
-
# </div>
-
#
-
# <%# app/views/users/index.html.erb %>
-
# <%= render layout: @users do |user| %>
-
# Title: <%= user.title %>
-
# <% end %>
-
#
-
# This will render the layout for each user and yield to the block, passing the user, each time.
-
#
-
# You can also yield multiple times in one layout and use block arguments to differentiate the sections.
-
#
-
# <%# app/views/users/_user.html.erb %>
-
# <div class="user">
-
# <%= yield user, :header %>
-
# Budget: $<%= user.budget %>
-
# <%= yield user, :footer %>
-
# </div>
-
#
-
# <%# app/views/users/index.html.erb %>
-
# <%= render layout: @users do |user, section| %>
-
# <%- case section when :header -%>
-
# Title: <%= user.title %>
-
# <%- when :footer -%>
-
# Deadline: <%= user.deadline %>
-
# <%- end -%>
-
# <% end %>
-
3
class PartialRenderer < AbstractRenderer
-
3
include CollectionCaching
-
-
3
def initialize(lookup_context, options)
-
super(lookup_context)
-
@options = options
-
@locals = @options[:locals] || {}
-
@details = extract_details(@options)
-
end
-
-
3
def render(partial, context, block)
-
template = find_template(partial, template_keys(partial))
-
-
if !block && (layout = @options[:layout])
-
layout = find_template(layout.to_s, template_keys(partial))
-
end
-
-
render_partial_template(context, @locals, template, layout, block)
-
end
-
-
3
private
-
3
def template_keys(_)
-
@locals.keys
-
end
-
-
3
def render_partial_template(view, locals, template, layout, block)
-
ActiveSupport::Notifications.instrument(
-
"render_partial.action_view",
-
identifier: template.identifier,
-
layout: layout && layout.virtual_path
-
) do |payload|
-
content = template.render(view, locals, add_to_stack: !block) do |*name|
-
view._layout_for(*name, &block)
-
end
-
-
content = layout.render(view, locals) { content } if layout
-
payload[:cache_hit] = view.view_renderer.cache_hits[template.virtual_path]
-
build_rendered_template(content, template)
-
end
-
end
-
-
3
def find_template(path, locals)
-
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
-
@lookup_context.find_template(path, prefixes, true, locals, @details)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/enumerable"
-
-
3
module ActionView
-
3
module CollectionCaching # :nodoc:
-
3
extend ActiveSupport::Concern
-
-
3
included do
-
# Fallback cache store if Action View is used without Rails.
-
# Otherwise overridden in Railtie to use Rails.cache.
-
3
mattr_accessor :collection_cache, default: ActiveSupport::Cache::MemoryStore.new
-
end
-
-
3
private
-
3
def will_cache?(options, view)
-
options[:cached] && view.controller.respond_to?(:perform_caching) && view.controller.perform_caching
-
end
-
-
3
def cache_collection_render(instrumentation_payload, view, template, collection)
-
return yield(collection) unless will_cache?(@options, view)
-
-
collection_iterator = collection
-
-
# Result is a hash with the key represents the
-
# key used for cache lookup and the value is the item
-
# on which the partial is being rendered
-
keyed_collection, ordered_keys = collection_by_cache_keys(view, template, collection)
-
-
# Pull all partials from cache
-
# Result is a hash, key matches the entry in
-
# `keyed_collection` where the cache was retrieved and the
-
# value is the value that was present in the cache
-
cached_partials = collection_cache.read_multi(*keyed_collection.keys)
-
instrumentation_payload[:cache_hits] = cached_partials.size
-
-
# Extract the items for the keys that are not found
-
collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
-
-
rendered_partials = collection.empty? ? [] : yield(collection_iterator.from_collection(collection))
-
-
index = 0
-
keyed_partials = fetch_or_cache_partial(cached_partials, template, order_by: keyed_collection.each_key) do
-
# This block is called once
-
# for every cache miss while preserving order.
-
rendered_partials[index].tap { index += 1 }
-
end
-
-
ordered_keys.map do |key|
-
keyed_partials[key]
-
end
-
end
-
-
3
def callable_cache_key?
-
@options[:cached].respond_to?(:call)
-
end
-
-
3
def collection_by_cache_keys(view, template, collection)
-
seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
-
-
digest_path = view.digest_path_from_template(template)
-
-
collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
-
key = expanded_cache_key(seed.call(item), view, template, digest_path)
-
ordered_keys << key
-
hash[key] = item
-
end
-
end
-
-
3
def expanded_cache_key(key, view, template, digest_path)
-
key = view.combined_fragment_cache_key(view.cache_fragment_name(key, digest_path: digest_path))
-
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
-
end
-
-
# `order_by` is an enumerable object containing keys of the cache,
-
# all keys are passed in whether found already or not.
-
#
-
# `cached_partials` is a hash. If the value exists
-
# it represents the rendered partial from the cache
-
# otherwise `Hash#fetch` will take the value of its block.
-
#
-
# This method expects a block that will return the rendered
-
# partial. An example is to render all results
-
# for each element that was not found in the cache and store it as an array.
-
# Order it so that the first empty cache element in `cached_partials`
-
# corresponds to the first element in `rendered_partials`.
-
#
-
# If the partial is not already cached it will also be
-
# written back to the underlying cache store.
-
3
def fetch_or_cache_partial(cached_partials, template, order_by:)
-
order_by.index_with do |cache_key|
-
if content = cached_partials[cache_key]
-
build_rendered_template(content, template)
-
else
-
yield.tap do |rendered_partial|
-
collection_cache.write(cache_key, rendered_partial.body)
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
# This is the main entry point for rendering. It basically delegates
-
# to other objects like TemplateRenderer and PartialRenderer which
-
# actually renders the template.
-
#
-
# The Renderer will parse the options from the +render+ or +render_body+
-
# method and render a partial or a template based on the options. The
-
# +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all
-
# the setup and logic necessary to render a view and a new object is created
-
# each time +render+ is called.
-
class Renderer
-
attr_accessor :lookup_context
-
-
def initialize(lookup_context)
-
@lookup_context = lookup_context
-
end
-
-
# Main render entry point shared by Action View and Action Controller.
-
def render(context, options)
-
render_to_object(context, options).body
-
end
-
-
def render_to_object(context, options) # :nodoc:
-
if options.key?(:partial)
-
render_partial_to_object(context, options)
-
elsif options.key?(:object)
-
object = options[:object]
-
AbstractRenderer::RenderedTemplate.new(object.render_in(context), object)
-
else
-
render_template_to_object(context, options)
-
end
-
end
-
-
# Render but returns a valid Rack body. If fibers are defined, we return
-
# a streaming body that renders the template piece by piece.
-
#
-
# Note that partials are not supported to be rendered with streaming,
-
# so in such cases, we just wrap them in an array.
-
def render_body(context, options)
-
if options.key?(:partial)
-
[render_partial(context, options)]
-
else
-
StreamingTemplateRenderer.new(@lookup_context).render(context, options)
-
end
-
end
-
-
# Direct access to template rendering.
-
def render_template(context, options) #:nodoc:
-
render_template_to_object(context, options).body
-
end
-
-
# Direct access to partial rendering.
-
def render_partial(context, options, &block) #:nodoc:
-
render_partial_to_object(context, options, &block).body
-
end
-
-
def cache_hits # :nodoc:
-
@cache_hits ||= {}
-
end
-
-
def render_template_to_object(context, options) #:nodoc:
-
TemplateRenderer.new(@lookup_context).render(context, options)
-
end
-
-
def render_partial_to_object(context, options, &block) #:nodoc:
-
partial = options[:partial]
-
if String === partial
-
collection = collection_from_options(options)
-
-
if collection
-
# Collection + Partial
-
renderer = CollectionRenderer.new(@lookup_context, options)
-
renderer.render_collection_with_partial(collection, partial, context, block)
-
else
-
if options.key?(:object)
-
# Object + Partial
-
renderer = ObjectRenderer.new(@lookup_context, options)
-
renderer.render_object_with_partial(options[:object], partial, context, block)
-
else
-
# Partial
-
renderer = PartialRenderer.new(@lookup_context, options)
-
renderer.render(partial, context, block)
-
end
-
end
-
else
-
collection = collection_from_object(partial) || collection_from_options(options)
-
-
if collection
-
# Collection + Derived Partial
-
renderer = CollectionRenderer.new(@lookup_context, options)
-
renderer.render_collection_derive_partial(collection, context, block)
-
else
-
# Object + Derived Partial
-
renderer = ObjectRenderer.new(@lookup_context, options)
-
renderer.render_object_derive_partial(partial, context, block)
-
end
-
end
-
end
-
-
private
-
def collection_from_options(options)
-
if options.key?(:collection)
-
collection = options[:collection]
-
collection || []
-
end
-
end
-
-
def collection_from_object(object)
-
object if object.respond_to?(:to_ary)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "fiber"
-
-
module ActionView
-
# == TODO
-
#
-
# * Support streaming from child templates, partials and so on.
-
# * Rack::Cache needs to support streaming bodies
-
class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
-
# A valid Rack::Body (i.e. it responds to each).
-
# It is initialized with a block that, when called, starts
-
# rendering the template.
-
class Body #:nodoc:
-
def initialize(&start)
-
@start = start
-
end
-
-
def each(&block)
-
begin
-
@start.call(block)
-
rescue Exception => exception
-
log_error(exception)
-
block.call ActionView::Base.streaming_completion_on_exception
-
end
-
self
-
end
-
-
private
-
# This is the same logging logic as in ShowExceptions middleware.
-
def log_error(exception)
-
logger = ActionView::Base.logger
-
return unless logger
-
-
message = +"\n#{exception.class} (#{exception.message}):\n"
-
message << exception.annotated_source_code.to_s if exception.respond_to?(:annotated_source_code)
-
message << " " << exception.backtrace.join("\n ")
-
logger.fatal("#{message}\n\n")
-
end
-
end
-
-
# For streaming, instead of rendering a given a template, we return a Body
-
# object that responds to each. This object is initialized with a block
-
# that knows how to render the template.
-
def render_template(view, template, layout_name = nil, locals = {}) #:nodoc:
-
return [super.body] unless layout_name && template.supports_streaming?
-
-
locals ||= {}
-
layout = layout_name && find_layout(layout_name, locals.keys, [formats.first])
-
-
Body.new do |buffer|
-
delayed_render(buffer, template, layout, view, locals)
-
end
-
end
-
-
private
-
def delayed_render(buffer, template, layout, view, locals)
-
# Wrap the given buffer in the StreamingBuffer and pass it to the
-
# underlying template handler. Now, every time something is concatenated
-
# to the buffer, it is not appended to an array, but streamed straight
-
# to the client.
-
output = ActionView::StreamingBuffer.new(buffer)
-
yielder = lambda { |*name| view._layout_for(*name) }
-
-
ActiveSupport::Notifications.instrument(
-
"render_template.action_view",
-
identifier: template.identifier,
-
layout: layout && layout.virtual_path
-
) do
-
outer_config = I18n.config
-
fiber = Fiber.new do
-
I18n.config = outer_config
-
if layout
-
layout.render(view, locals, output, &yielder)
-
else
-
# If you don't have a layout, just render the thing
-
# and concatenate the final result. This is the same
-
# as a layout with just <%= yield %>
-
output.safe_concat view._layout_for
-
end
-
end
-
-
# Set the view flow to support streaming. It will be aware
-
# when to stop rendering the layout because it needs to search
-
# something in the template and vice-versa.
-
view.view_flow = StreamingFlow.new(view, fiber)
-
-
# Yo! Start the fiber!
-
fiber.resume
-
-
# If the fiber is still alive, it means we need something
-
# from the template, so start rendering it. If not, it means
-
# the layout exited without requiring anything from the template.
-
if fiber.alive?
-
content = template.render(view, locals, &yielder)
-
-
# Once rendering the template is done, sets its content in the :layout key.
-
view.view_flow.set(:layout, content)
-
-
# In case the layout continues yielding, we need to resume
-
# the fiber until all yields are handled.
-
fiber.resume while fiber.alive?
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
class TemplateRenderer < AbstractRenderer #:nodoc:
-
def render(context, options)
-
@details = extract_details(options)
-
template = determine_template(options)
-
-
prepend_formats(template.format)
-
-
render_template(context, template, options[:layout], options[:locals] || {})
-
end
-
-
private
-
# Determine the template to be rendered using the given options.
-
def determine_template(options)
-
keys = options.has_key?(:locals) ? options[:locals].keys : []
-
-
if options.key?(:body)
-
Template::Text.new(options[:body])
-
elsif options.key?(:plain)
-
Template::Text.new(options[:plain])
-
elsif options.key?(:html)
-
Template::HTML.new(options[:html], formats.first)
-
elsif options.key?(:file)
-
if File.exist?(options[:file])
-
Template::RawFile.new(options[:file])
-
else
-
ActiveSupport::Deprecation.warn "render file: should be given the absolute path to a file"
-
@lookup_context.with_fallbacks.find_template(options[:file], nil, false, keys, @details)
-
end
-
elsif options.key?(:inline)
-
handler = Template.handler_for_extension(options[:type] || "erb")
-
format = if handler.respond_to?(:default_format)
-
handler.default_format
-
else
-
@lookup_context.formats.first
-
end
-
Template::Inline.new(options[:inline], "inline template", handler, locals: keys, format: format)
-
elsif options.key?(:template)
-
if options[:template].respond_to?(:render)
-
options[:template]
-
else
-
@lookup_context.find_template(options[:template], options[:prefixes], false, keys, @details)
-
end
-
else
-
raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
-
end
-
end
-
-
# Renders the given template. A string representing the layout can be
-
# supplied as well.
-
def render_template(view, template, layout_name, locals)
-
render_with_layout(view, template, layout_name, locals) do |layout|
-
ActiveSupport::Notifications.instrument(
-
"render_template.action_view",
-
identifier: template.identifier,
-
layout: layout && layout.virtual_path
-
) do
-
template.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
end
-
end
-
-
def render_with_layout(view, template, path, locals)
-
layout = path && find_layout(path, locals.keys, [formats.first])
-
-
body = if layout
-
ActiveSupport::Notifications.instrument("render_layout.action_view", identifier: layout.identifier) do
-
view.view_flow.set(:layout, yield(layout))
-
layout.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
else
-
yield
-
end
-
build_rendered_template(body, template)
-
end
-
-
# This is the method which actually finds the layout using details in the lookup
-
# context object. If no layout is found, it checks if at least a layout with
-
# the given name exists across all details before raising the error.
-
def find_layout(layout, keys, formats)
-
resolve_layout(layout, keys, formats)
-
end
-
-
def resolve_layout(layout, keys, formats)
-
details = @details.dup
-
details[:formats] = formats
-
-
case layout
-
when String
-
begin
-
if layout.start_with?("/")
-
ActiveSupport::Deprecation.warn "Rendering layouts from an absolute path is deprecated."
-
@lookup_context.with_fallbacks.find_template(layout, nil, false, [], details)
-
else
-
@lookup_context.find_template(layout, nil, false, [], details)
-
end
-
rescue ActionView::MissingTemplate
-
all_details = @details.merge(formats: @lookup_context.default_formats)
-
raise unless template_exists?(layout, nil, false, [], **all_details)
-
end
-
when Proc
-
resolve_layout(layout.call(@lookup_context, formats), keys, formats)
-
else
-
layout
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "action_view/view_paths"
-
-
9
module ActionView
-
# This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
-
# it will trigger the lookup_context and consequently expire the cache.
-
9
class I18nProxy < ::I18n::Config #:nodoc:
-
9
attr_reader :original_config, :lookup_context
-
-
9
def initialize(original_config, lookup_context)
-
original_config = original_config.original_config if original_config.respond_to?(:original_config)
-
@original_config, @lookup_context = original_config, lookup_context
-
end
-
-
9
def locale
-
@original_config.locale
-
end
-
-
9
def locale=(value)
-
@lookup_context.locale = value
-
end
-
end
-
-
9
module Rendering
-
9
extend ActiveSupport::Concern
-
9
include ActionView::ViewPaths
-
-
9
attr_reader :rendered_format
-
-
9
def initialize
-
@rendered_format = nil
-
super
-
end
-
-
# Overwrite process to set up I18n proxy.
-
9
def process(*) #:nodoc:
-
old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
-
super
-
ensure
-
I18n.config = old_config
-
end
-
-
9
module ClassMethods
-
9
def _routes
-
end
-
-
9
def _helpers
-
end
-
-
9
def build_view_context_class(klass, supports_path, routes, helpers)
-
Class.new(klass) do
-
if routes
-
include routes.url_helpers(supports_path)
-
include routes.mounted_helpers
-
end
-
-
if helpers
-
include helpers
-
end
-
end
-
end
-
-
9
def view_context_class
-
klass = ActionView::LookupContext::DetailsKey.view_context_class(ActionView::Base)
-
-
@view_context_class ||= build_view_context_class(klass, supports_path?, _routes, _helpers)
-
-
if klass.changed?(@view_context_class)
-
@view_context_class = build_view_context_class(klass, supports_path?, _routes, _helpers)
-
end
-
-
@view_context_class
-
end
-
end
-
-
9
def view_context_class
-
self.class.view_context_class
-
end
-
-
# An instance of a view class. The default view class is ActionView::Base.
-
#
-
# The view class must have the following methods:
-
#
-
# * <tt>View.new(lookup_context, assigns, controller)</tt> — Create a new
-
# ActionView instance for a controller and we can also pass the arguments.
-
#
-
# * <tt>View#render(option)</tt> — Returns String with the rendered template.
-
#
-
# Override this method in a module to change the default behavior.
-
9
def view_context
-
view_context_class.new(lookup_context, view_assigns, self)
-
end
-
-
# Returns an object that is able to render templates.
-
9
def view_renderer # :nodoc:
-
# Lifespan: Per controller
-
@_view_renderer ||= ActionView::Renderer.new(lookup_context)
-
end
-
-
9
def render_to_body(options = {})
-
_process_options(options)
-
_render_template(options)
-
end
-
-
9
private
-
# Find and render a template based on the options given.
-
9
def _render_template(options)
-
variant = options.delete(:variant)
-
assigns = options.delete(:assigns)
-
context = view_context
-
-
context.assign assigns if assigns
-
lookup_context.variants = variant if variant
-
-
rendered_template = context.in_rendering_context(options) do |renderer|
-
renderer.render_to_object(context, options)
-
end
-
-
rendered_format = rendered_template.format || lookup_context.formats.first
-
@rendered_format = Template::Types[rendered_format]
-
-
rendered_template.body
-
end
-
-
# Assign the rendered format to look up context.
-
9
def _process_format(format)
-
super
-
lookup_context.formats = [format.to_sym] if format.to_sym
-
end
-
-
# Normalize args by converting render "foo" to render :action => "foo" and
-
# render "foo/bar" to render :template => "foo/bar".
-
9
def _normalize_args(action = nil, options = {})
-
options = super(action, options)
-
case action
-
when NilClass
-
when Hash
-
options = action
-
when String, Symbol
-
action = action.to_s
-
key = action.include?(?/) ? :template : :action
-
options[key] = action
-
else
-
if action.respond_to?(:permitted?) && action.permitted?
-
options = action
-
elsif action.respond_to?(:render_in)
-
options[:object] = action
-
else
-
options[:partial] = action
-
end
-
end
-
-
options
-
end
-
-
# Normalize options.
-
9
def _normalize_options(options)
-
options = super(options)
-
if options[:partial] == true
-
options[:partial] = action_name
-
end
-
-
if (options.keys & [:partial, :file, :template]).empty?
-
options[:prefixes] ||= _prefixes
-
end
-
-
options[:template] ||= (options[:action] || action_name).to_s
-
options
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "action_dispatch/routing/polymorphic_routes"
-
-
9
module ActionView
-
9
module RoutingUrlFor
-
# Returns the URL for the set of +options+ provided. This takes the
-
# same options as +url_for+ in Action Controller (see the
-
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
-
# <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action"
-
# instead of the fully qualified URL like "http://example.com/controller/action".
-
#
-
# ==== Options
-
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
-
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
-
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
-
# is currently not recommended since it breaks caching.
-
# * <tt>:host</tt> - Overrides the default (current) host if provided.
-
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
-
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
-
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
-
#
-
# ==== Relying on named routes
-
#
-
# Passing a record (like an Active Record) instead of a hash as the options parameter will
-
# trigger the named route for that record. The lookup will happen on the name of the class. So passing a
-
# Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
-
# +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).
-
#
-
# ==== Implicit Controller Namespacing
-
#
-
# Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one.
-
#
-
# ==== Examples
-
# <%= url_for(action: 'index') %>
-
# # => /blogs/
-
#
-
# <%= url_for(action: 'find', controller: 'books') %>
-
# # => /books/find
-
#
-
# <%= url_for(action: 'login', controller: 'members', only_path: false, protocol: 'https') %>
-
# # => https://www.example.com/members/login/
-
#
-
# <%= url_for(action: 'play', anchor: 'player') %>
-
# # => /messages/play/#player
-
#
-
# <%= url_for(action: 'jump', anchor: 'tax&ship') %>
-
# # => /testing/jump/#tax&ship
-
#
-
# <%= url_for(Workshop.new) %>
-
# # relies on Workshop answering a persisted? call (and in this case returning false)
-
# # => /workshops
-
#
-
# <%= url_for(@workshop) %>
-
# # calls @workshop.to_param which by default returns the id
-
# # => /workshops/5
-
#
-
# # to_param can be re-defined in a model to provide different URL names:
-
# # => /workshops/1-workshop-name
-
#
-
# <%= url_for("http://www.example.com") %>
-
# # => http://www.example.com
-
#
-
# <%= url_for(:back) %>
-
# # if request.env["HTTP_REFERER"] is set to "http://www.example.com"
-
# # => http://www.example.com
-
#
-
# <%= url_for(:back) %>
-
# # if request.env["HTTP_REFERER"] is not set or is blank
-
# # => javascript:history.back()
-
#
-
# <%= url_for(action: 'index', controller: 'users') %>
-
# # Assuming an "admin" namespace
-
# # => /admin/users
-
#
-
# <%= url_for(action: 'index', controller: '/users') %>
-
# # Specify absolute path with beginning slash
-
# # => /users
-
9
def url_for(options = nil)
-
case options
-
when String
-
options
-
when nil
-
super(only_path: _generate_paths_by_default)
-
when Hash
-
options = options.symbolize_keys
-
ensure_only_path_option(options)
-
-
super(options)
-
when ActionController::Parameters
-
ensure_only_path_option(options)
-
-
super(options)
-
when :back
-
_back_url
-
when Array
-
components = options.dup
-
options = components.extract_options!
-
ensure_only_path_option(options)
-
-
if options[:only_path]
-
polymorphic_path(components, options)
-
else
-
polymorphic_url(components, options)
-
end
-
else
-
method = _generate_paths_by_default ? :path : :url
-
builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method)
-
-
case options
-
when Symbol
-
builder.handle_string_call(self, options)
-
when Class
-
builder.handle_class_call(self, options)
-
else
-
builder.handle_model_call(self, options)
-
end
-
end
-
end
-
-
9
def url_options #:nodoc:
-
return super unless controller.respond_to?(:url_options)
-
controller.url_options
-
end
-
-
9
private
-
9
def _routes_context
-
controller
-
end
-
-
9
def optimize_routes_generation?
-
controller.respond_to?(:optimize_routes_generation?, true) ?
-
controller.optimize_routes_generation? : super
-
end
-
-
9
def _generate_paths_by_default
-
true
-
end
-
-
9
def ensure_only_path_option(options)
-
unless options.key?(:only_path)
-
options[:only_path] = _generate_paths_by_default unless options[:host]
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "thread"
-
9
require "delegate"
-
-
9
module ActionView
-
# = Action View Template
-
9
class Template
-
9
extend ActiveSupport::Autoload
-
-
9
def self.finalize_compiled_template_methods
-
ActiveSupport::Deprecation.warn "ActionView::Template.finalize_compiled_template_methods is deprecated and has no effect"
-
end
-
-
9
def self.finalize_compiled_template_methods=(_)
-
ActiveSupport::Deprecation.warn "ActionView::Template.finalize_compiled_template_methods= is deprecated and has no effect"
-
end
-
-
# === Encodings in ActionView::Template
-
#
-
# ActionView::Template is one of a few sources of potential
-
# encoding issues in Rails. This is because the source for
-
# templates are usually read from disk, and Ruby (like most
-
# encoding-aware programming languages) assumes that the
-
# String retrieved through File IO is encoded in the
-
# <tt>default_external</tt> encoding. In Rails, the default
-
# <tt>default_external</tt> encoding is UTF-8.
-
#
-
# As a result, if a user saves their template as ISO-8859-1
-
# (for instance, using a non-Unicode-aware text editor),
-
# and uses characters outside of the ASCII range, their
-
# users will see diamonds with question marks in them in
-
# the browser.
-
#
-
# For the rest of this documentation, when we say "UTF-8",
-
# we mean "UTF-8 or whatever the default_internal encoding
-
# is set to". By default, it will be UTF-8.
-
#
-
# To mitigate this problem, we use a few strategies:
-
# 1. If the source is not valid UTF-8, we raise an exception
-
# when the template is compiled to alert the user
-
# to the problem.
-
# 2. The user can specify the encoding using Ruby-style
-
# encoding comments in any template engine. If such
-
# a comment is supplied, Rails will apply that encoding
-
# to the resulting compiled source returned by the
-
# template handler.
-
# 3. In all cases, we transcode the resulting String to
-
# the UTF-8.
-
#
-
# This means that other parts of Rails can always assume
-
# that templates are encoded in UTF-8, even if the original
-
# source of the template was not UTF-8.
-
#
-
# From a user's perspective, the easiest thing to do is
-
# to save your templates as UTF-8. If you do this, you
-
# do not need to do anything else for things to "just work".
-
#
-
# === Instructions for template handlers
-
#
-
# The easiest thing for you to do is to simply ignore
-
# encodings. Rails will hand you the template source
-
# as the default_internal (generally UTF-8), raising
-
# an exception for the user before sending the template
-
# to you if it could not determine the original encoding.
-
#
-
# For the greatest simplicity, you can support only
-
# UTF-8 as the <tt>default_internal</tt>. This means
-
# that from the perspective of your handler, the
-
# entire pipeline is just UTF-8.
-
#
-
# === Advanced: Handlers with alternate metadata sources
-
#
-
# If you want to provide an alternate mechanism for
-
# specifying encodings (like ERB does via <%# encoding: ... %>),
-
# you may indicate that you will handle encodings yourself
-
# by implementing <tt>handles_encoding?</tt> on your handler.
-
#
-
# If you do, Rails will not try to encode the String
-
# into the default_internal, passing you the unaltered
-
# bytes tagged with the assumed encoding (from
-
# default_external).
-
#
-
# In this case, make sure you return a String from
-
# your handler encoded in the default_internal. Since
-
# you are handling out-of-band metadata, you are
-
# also responsible for alerting the user to any
-
# problems with converting the user's data to
-
# the <tt>default_internal</tt>.
-
#
-
# To do so, simply raise +WrongEncodingError+ as follows:
-
#
-
# raise WrongEncodingError.new(
-
# problematic_string,
-
# expected_encoding
-
# )
-
-
##
-
# :method: local_assigns
-
#
-
# Returns a hash with the defined local variables.
-
#
-
# Given this sub template rendering:
-
#
-
# <%= render "shared/header", { headline: "Welcome", person: person } %>
-
#
-
# You can use +local_assigns+ in the sub templates to access the local variables:
-
#
-
# local_assigns[:headline] # => "Welcome"
-
-
9
eager_autoload do
-
9
autoload :Error
-
9
autoload :RawFile
-
9
autoload :Handlers
-
9
autoload :HTML
-
9
autoload :Inline
-
9
autoload :Sources
-
9
autoload :Text
-
9
autoload :Types
-
end
-
-
9
extend Template::Handlers
-
-
9
attr_reader :identifier, :handler, :original_encoding, :updated_at
-
9
attr_reader :variable, :format, :variant, :locals, :virtual_path
-
-
9
def initialize(source, identifier, handler, format: nil, variant: nil, locals: nil, virtual_path: nil, updated_at: nil)
-
unless locals
-
ActiveSupport::Deprecation.warn "ActionView::Template#initialize requires a locals parameter"
-
locals = []
-
end
-
-
@source = source
-
@identifier = identifier
-
@handler = handler
-
@compiled = false
-
@locals = locals
-
@virtual_path = virtual_path
-
-
@variable = if @virtual_path
-
base = @virtual_path.end_with?("/") ? "" : ::File.basename(@virtual_path)
-
base =~ /\A_?(.*?)(?:\.\w+)*\z/
-
$1.to_sym
-
end
-
-
if updated_at
-
ActiveSupport::Deprecation.warn "ActionView::Template#updated_at is deprecated"
-
@updated_at = updated_at
-
else
-
@updated_at = Time.now
-
end
-
@format = format
-
@variant = variant
-
@compile_mutex = Mutex.new
-
end
-
-
9
deprecate :original_encoding
-
9
deprecate :updated_at
-
9
deprecate def virtual_path=(_); end
-
9
deprecate def locals=(_); end
-
9
deprecate def formats=(_); end
-
9
deprecate def formats; Array(format); end
-
9
deprecate def variants=(_); end
-
9
deprecate def variants; [variant]; end
-
9
deprecate def refresh(_); self; end
-
-
# Returns whether the underlying handler supports streaming. If so,
-
# a streaming buffer *may* be passed when it starts rendering.
-
9
def supports_streaming?
-
handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
-
end
-
-
# Render a template. If the template was not compiled yet, it is done
-
# exactly before rendering.
-
#
-
# This method is instrumented as "!render_template.action_view". Notice that
-
# we use a bang in this instrumentation because you don't want to
-
# consume this in production. This is only slow if it's being listened to.
-
9
def render(view, locals, buffer = ActionView::OutputBuffer.new, add_to_stack: true, &block)
-
instrument_render_template do
-
compile!(view)
-
view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, &block)
-
end
-
rescue => e
-
handle_render_error(view, e)
-
end
-
-
9
def type
-
@type ||= Types[format]
-
end
-
-
9
def short_identifier
-
@short_identifier ||= defined?(Rails.root) ? identifier.delete_prefix("#{Rails.root}/") : identifier
-
end
-
-
9
def inspect
-
"#<#{self.class.name} #{short_identifier} locals=#{@locals.inspect}>"
-
end
-
-
9
def source
-
@source.to_s
-
end
-
-
# This method is responsible for properly setting the encoding of the
-
# source. Until this point, we assume that the source is BINARY data.
-
# If no additional information is supplied, we assume the encoding is
-
# the same as <tt>Encoding.default_external</tt>.
-
#
-
# The user can also specify the encoding via a comment on the first
-
# line of the template (# encoding: NAME-OF-ENCODING). This will work
-
# with any template engine, as we process out the encoding comment
-
# before passing the source on to the template engine, leaving a
-
# blank line in its stead.
-
9
def encode!
-
source = self.source
-
-
return source unless source.encoding == Encoding::BINARY
-
-
# Look for # encoding: *. If we find one, we'll encode the
-
# String in that encoding, otherwise, we'll use the
-
# default external encoding.
-
if source.sub!(/\A#{ENCODING_FLAG}/, "")
-
encoding = magic_encoding = $1
-
else
-
encoding = Encoding.default_external
-
end
-
-
# Tag the source with the default external encoding
-
# or the encoding specified in the file
-
source.force_encoding(encoding)
-
-
# If the user didn't specify an encoding, and the handler
-
# handles encodings, we simply pass the String as is to
-
# the handler (with the default_external tag)
-
if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
-
source
-
# Otherwise, if the String is valid in the encoding,
-
# encode immediately to default_internal. This means
-
# that if a handler doesn't handle encodings, it will
-
# always get Strings in the default_internal
-
elsif source.valid_encoding?
-
source.encode!
-
# Otherwise, since the String is invalid in the encoding
-
# specified, raise an exception
-
else
-
raise WrongEncodingError.new(source, encoding)
-
end
-
end
-
-
-
# Exceptions are marshalled when using the parallel test runner with DRb, so we need
-
# to ensure that references to the template object can be marshalled as well. This means forgoing
-
# the marshalling of the compiler mutex and instantiating that again on unmarshalling.
-
9
def marshal_dump # :nodoc:
-
[ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant ]
-
end
-
-
9
def marshal_load(array) # :nodoc:
-
@source, @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant = *array
-
@compile_mutex = Mutex.new
-
end
-
-
9
private
-
# Compile a template. This method ensures a template is compiled
-
# just once and removes the source after it is compiled.
-
9
def compile!(view)
-
return if @compiled
-
-
# Templates can be used concurrently in threaded environments
-
# so compilation and any instance variable modification must
-
# be synchronized
-
@compile_mutex.synchronize do
-
# Any thread holding this lock will be compiling the template needed
-
# by the threads waiting. So re-check the @compiled flag to avoid
-
# re-compilation
-
return if @compiled
-
-
mod = view.compiled_method_container
-
-
instrument("!compile_template") do
-
compile(mod)
-
end
-
-
@compiled = true
-
end
-
end
-
-
9
class LegacyTemplate < DelegateClass(Template) # :nodoc:
-
9
attr_reader :source
-
-
9
def initialize(template, source)
-
super(template)
-
@source = source
-
end
-
end
-
-
# Among other things, this method is responsible for properly setting
-
# the encoding of the compiled template.
-
#
-
# If the template engine handles encodings, we send the encoded
-
# String to the engine without further processing. This allows
-
# the template engine to support additional mechanisms for
-
# specifying the encoding. For instance, ERB supports <%# encoding: %>
-
#
-
# Otherwise, after we figure out the correct encoding, we then
-
# encode the source into <tt>Encoding.default_internal</tt>.
-
# In general, this means that templates will be UTF-8 inside of Rails,
-
# regardless of the original source encoding.
-
9
def compile(mod)
-
source = encode!
-
code = @handler.call(self, source)
-
-
# Make sure that the resulting String to be eval'd is in the
-
# encoding of the code
-
original_source = source
-
source = +<<-end_src
-
def #{method_name}(local_assigns, output_buffer)
-
@virtual_path = #{@virtual_path.inspect};#{locals_code};#{code}
-
end
-
end_src
-
-
# Make sure the source is in the encoding of the returned code
-
source.force_encoding(code.encoding)
-
-
# In case we get back a String from a handler that is not in
-
# BINARY or the default_internal, encode it to the default_internal
-
source.encode!
-
-
# Now, validate that the source we got back from the template
-
# handler is valid in the default_internal. This is for handlers
-
# that handle encoding but screw up
-
unless source.valid_encoding?
-
raise WrongEncodingError.new(source, Encoding.default_internal)
-
end
-
-
start_line = @handler.respond_to?(:start_line) ? @handler.start_line(self) : 0
-
-
begin
-
mod.module_eval(source, identifier, start_line)
-
rescue SyntaxError
-
# Account for when code in the template is not syntactically valid; e.g. if we're using
-
# ERB and the user writes <%= foo( %>, attempting to call a helper `foo` and interpolate
-
# the result into the template, but missing an end parenthesis.
-
raise SyntaxErrorInTemplate.new(self, original_source)
-
end
-
end
-
-
9
def handle_render_error(view, e)
-
if e.is_a?(Template::Error)
-
e.sub_template_of(self)
-
raise e
-
else
-
raise Template::Error.new(self)
-
end
-
end
-
-
9
def locals_code
-
# Only locals with valid variable names get set directly. Others will
-
# still be available in local_assigns.
-
locals = @locals - Module::RUBY_RESERVED_KEYWORDS
-
locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
-
-
# Assign for the same variable is to suppress unused variable warning
-
locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
-
end
-
-
9
def method_name
-
@method_name ||= begin
-
m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
-
m.tr!("-", "_")
-
m
-
end
-
end
-
-
9
def identifier_method_name
-
short_identifier.tr("^a-z_", "_")
-
end
-
-
9
def instrument(action, &block) # :doc:
-
ActiveSupport::Notifications.instrument("#{action}.action_view", instrument_payload, &block)
-
end
-
-
9
def instrument_render_template(&block)
-
ActiveSupport::Notifications.instrument("!render_template.action_view", instrument_payload, &block)
-
end
-
-
9
def instrument_payload
-
{ virtual_path: @virtual_path, identifier: @identifier }
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "active_support/core_ext/enumerable"
-
-
module ActionView
-
# = Action View Errors
-
class ActionViewError < StandardError #:nodoc:
-
end
-
-
class EncodingError < StandardError #:nodoc:
-
end
-
-
class WrongEncodingError < EncodingError #:nodoc:
-
def initialize(string, encoding)
-
@string, @encoding = string, encoding
-
end
-
-
def message
-
@string.force_encoding(Encoding::ASCII_8BIT)
-
"Your template was not saved as valid #{@encoding}. Please " \
-
"either specify #{@encoding} as the encoding for your template " \
-
"in your text editor, or mark the template with its " \
-
"encoding by inserting the following as the first line " \
-
"of the template:\n\n# encoding: <name of correct encoding>.\n\n" \
-
"The source of your template was:\n\n#{@string}"
-
end
-
end
-
-
class MissingTemplate < ActionViewError #:nodoc:
-
attr_reader :path
-
-
def initialize(paths, path, prefixes, partial, details, *)
-
@path = path
-
prefixes = Array(prefixes)
-
template_type = if partial
-
"partial"
-
elsif /layouts/i.match?(path)
-
"layout"
-
else
-
"template"
-
end
-
-
if partial && path.present?
-
path = path.sub(%r{([^/]+)$}, "_\\1")
-
end
-
searched_paths = prefixes.map { |prefix| [prefix, path].join("/") }
-
-
out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n"
-
out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join
-
super out
-
end
-
end
-
-
class Template
-
# The Template::Error exception is raised when the compilation or rendering of the template
-
# fails. This exception then gathers a bunch of intimate details and uses it to report a
-
# precise exception message.
-
class Error < ActionViewError #:nodoc:
-
SOURCE_CODE_RADIUS = 3
-
-
# Override to prevent #cause resetting during re-raise.
-
attr_reader :cause
-
-
def initialize(template)
-
super($!.message)
-
set_backtrace($!.backtrace)
-
@cause = $!
-
@template, @sub_templates = template, nil
-
end
-
-
def file_name
-
@template.identifier
-
end
-
-
def sub_template_message
-
if @sub_templates
-
"Trace of template inclusion: " +
-
@sub_templates.collect(&:inspect).join(", ")
-
else
-
""
-
end
-
end
-
-
def source_extract(indentation = 0)
-
return [] unless num = line_number
-
num = num.to_i
-
-
source_code = @template.encode!.split("\n")
-
-
start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max
-
end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min
-
-
indent = end_on_line.to_s.size + indentation
-
return [] unless source_code = source_code[start_on_line..end_on_line]
-
-
formatted_code_for(source_code, start_on_line, indent)
-
end
-
-
def sub_template_of(template_path)
-
@sub_templates ||= []
-
@sub_templates << template_path
-
end
-
-
def line_number
-
@line_number ||=
-
if file_name
-
regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/
-
$1 if message =~ regexp || backtrace.find { |line| line =~ regexp }
-
end
-
end
-
-
def annotated_source_code
-
source_extract(4)
-
end
-
-
private
-
def source_location
-
if line_number
-
"on line ##{line_number} of "
-
else
-
"in "
-
end + file_name
-
end
-
-
def formatted_code_for(source_code, line_counter, indent)
-
indent_template = "%#{indent}s: %s"
-
source_code.map do |line|
-
line_counter += 1
-
indent_template % [line_counter, line]
-
end
-
end
-
end
-
end
-
-
TemplateError = Template::Error
-
-
class SyntaxErrorInTemplate < TemplateError #:nodoc
-
def initialize(template, offending_code_string)
-
@offending_code_string = offending_code_string
-
super(template)
-
end
-
-
def message
-
<<~MESSAGE
-
Encountered a syntax error while rendering template: check #{@offending_code_string}
-
MESSAGE
-
end
-
-
def annotated_source_code
-
@offending_code_string.split("\n").map.with_index(1) { |line, index|
-
indentation = " " * 4
-
"#{index}:#{indentation}#{line}"
-
}
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView #:nodoc:
-
# = Action View Template Handlers
-
9
class Template #:nodoc:
-
9
module Handlers #:nodoc:
-
9
autoload :Raw, "action_view/template/handlers/raw"
-
9
autoload :ERB, "action_view/template/handlers/erb"
-
9
autoload :Html, "action_view/template/handlers/html"
-
9
autoload :Builder, "action_view/template/handlers/builder"
-
-
9
def self.extended(base)
-
9
base.register_default_template_handler :raw, Raw.new
-
9
base.register_template_handler :erb, ERB.new
-
9
base.register_template_handler :html, Html.new
-
9
base.register_template_handler :builder, Builder.new
-
9
base.register_template_handler :ruby, lambda { |_, source| source }
-
end
-
-
9
@@template_handlers = {}
-
9
@@default_template_handlers = nil
-
-
9
def self.extensions
-
@@template_extensions ||= @@template_handlers.keys
-
end
-
-
9
class LegacyHandlerWrapper < SimpleDelegator # :nodoc:
-
9
def call(view, source)
-
__getobj__.call(ActionView::Template::LegacyTemplate.new(view, source))
-
end
-
end
-
-
# Register an object that knows how to handle template files with the given
-
# extensions. This can be used to implement new template types.
-
# The handler must respond to +:call+, which will be passed the template
-
# and should return the rendered template as a String.
-
9
def register_template_handler(*extensions, handler)
-
45
params = if handler.is_a?(Proc)
-
9
handler.parameters
-
else
-
36
handler.method(:call).parameters
-
end
-
-
135
unless params.find_all { |type, _| type == :req || type == :opt }.length >= 2
-
ActiveSupport::Deprecation.warn <<~eowarn
-
Single arity template handlers are deprecated. Template handlers must
-
now accept two parameters, the view object and the source for the view object.
-
Change:
-
>> #{handler}.call(#{params.map(&:last).join(", ")})
-
To:
-
>> #{handler}.call(#{params.map(&:last).join(", ")}, source)
-
eowarn
-
handler = LegacyHandlerWrapper.new(handler)
-
end
-
-
45
raise(ArgumentError, "Extension is required") if extensions.empty?
-
45
extensions.each do |extension|
-
45
@@template_handlers[extension.to_sym] = handler
-
end
-
45
@@template_extensions = nil
-
end
-
-
# Opposite to register_template_handler.
-
9
def unregister_template_handler(*extensions)
-
extensions.each do |extension|
-
handler = @@template_handlers.delete extension.to_sym
-
@@default_template_handlers = nil if @@default_template_handlers == handler
-
end
-
@@template_extensions = nil
-
end
-
-
9
def template_handler_extensions
-
@@template_handlers.keys.map(&:to_s).sort
-
end
-
-
9
def registered_template_handler(extension)
-
3
extension && @@template_handlers[extension.to_sym]
-
end
-
-
9
def register_default_template_handler(extension, klass)
-
9
register_template_handler(extension, klass)
-
9
@@default_template_handlers = klass
-
end
-
-
9
def handler_for_extension(extension)
-
3
registered_template_handler(extension) || @@default_template_handlers
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
9
module Template::Handlers
-
9
class Builder
-
9
class_attribute :default_format, default: :xml
-
-
9
def call(template, source)
-
require_engine
-
"xml = ::Builder::XmlMarkup.new(:indent => 2);" \
-
"self.output_buffer = xml.target!;" +
-
source +
-
";xml.target!;"
-
end
-
-
9
private
-
9
def require_engine # :doc:
-
@required ||= begin
-
require "builder"
-
true
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
9
class Template
-
9
module Handlers
-
9
class ERB
-
9
autoload :Erubi, "action_view/template/handlers/erb/erubi"
-
-
# Specify trim mode for the ERB compiler. Defaults to '-'.
-
# See ERB documentation for suitable values.
-
9
class_attribute :erb_trim_mode, default: "-"
-
-
# Default implementation used.
-
9
class_attribute :erb_implementation, default: Erubi
-
-
# Do not escape templates of these mime types.
-
9
class_attribute :escape_ignore_list, default: ["text/plain"]
-
-
9
[self, singleton_class].each do |base|
-
18
base.alias_method :escape_whitelist, :escape_ignore_list
-
18
base.alias_method :escape_whitelist=, :escape_ignore_list=
-
-
18
base.deprecate(
-
escape_whitelist: "use #escape_ignore_list instead",
-
:escape_whitelist= => "use #escape_ignore_list= instead"
-
)
-
end
-
-
9
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
-
-
9
def self.call(template, source)
-
new.call(template, source)
-
end
-
-
9
def supports_streaming?
-
true
-
end
-
-
9
def handles_encoding?
-
true
-
end
-
-
# Line number to pass to #module_eval
-
#
-
# If we're annotating the template, we need to offset the starting
-
# line number passed to #module_eval so that errors in the template
-
# will be raised on the correct line.
-
9
def start_line(template)
-
annotate?(template) ? -1 : 0
-
end
-
-
9
def call(template, source)
-
# First, convert to BINARY, so in case the encoding is
-
# wrong, we can still find an encoding tag
-
# (<%# encoding %>) inside the String using a regular
-
# expression
-
template_source = source.b
-
-
erb = template_source.gsub(ENCODING_TAG, "")
-
encoding = $2
-
-
erb.force_encoding valid_encoding(source.dup, encoding)
-
-
# Always make sure we return a String in the default_internal
-
erb.encode!
-
-
options = {
-
escape: (self.class.escape_ignore_list.include? template.type),
-
trim: (self.class.erb_trim_mode == "-")
-
}
-
-
if annotate?(template)
-
options[:preamble] = "@output_buffer.safe_append='<!-- BEGIN #{template.short_identifier} -->\n';"
-
options[:postamble] = "@output_buffer.safe_append='<!-- END #{template.short_identifier} -->\n';@output_buffer.to_s"
-
end
-
-
self.class.erb_implementation.new(erb, options).src
-
end
-
-
9
private
-
9
def annotate?(template)
-
ActionView::Base.annotate_rendered_view_with_filenames && template.format == :html
-
end
-
-
9
def valid_encoding(string, encoding)
-
# If a magic encoding comment was found, tag the
-
# String with this encoding. This is for a case
-
# where the original String was assumed to be,
-
# for instance, UTF-8, but a magic comment
-
# proved otherwise
-
string.force_encoding(encoding) if encoding
-
-
# If the String is valid, return the encoding we found
-
return string.encoding if string.valid_encoding?
-
-
# Otherwise, raise an exception
-
raise WrongEncodingError.new(string, string.encoding)
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "erubi"
-
-
9
module ActionView
-
9
class Template
-
9
module Handlers
-
9
class ERB
-
9
class Erubi < ::Erubi::Engine
-
# :nodoc: all
-
9
def initialize(input, properties = {})
-
@newline_pending = 0
-
-
# Dup properties so that we don't modify argument
-
properties = Hash[properties]
-
-
properties[:bufvar] ||= "@output_buffer"
-
properties[:preamble] ||= ""
-
properties[:postamble] ||= "#{properties[:bufvar]}.to_s"
-
-
properties[:escapefunc] = ""
-
-
super
-
end
-
-
9
def evaluate(action_view_erb_handler_context)
-
src = @src
-
view = Class.new(ActionView::Base) {
-
include action_view_erb_handler_context._routes.url_helpers
-
class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
-
}.empty
-
view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
-
end
-
-
9
private
-
9
def add_text(text)
-
return if text.empty?
-
-
if text == "\n"
-
@newline_pending += 1
-
else
-
src << bufvar << ".safe_append='"
-
src << "\n" * @newline_pending if @newline_pending > 0
-
src << text.gsub(/['\\]/, '\\\\\&')
-
src << "'.freeze;"
-
-
@newline_pending = 0
-
end
-
end
-
-
9
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
-
-
9
def add_expression(indicator, code)
-
flush_newline_if_pending(src)
-
-
if (indicator == "==") || @escape
-
src << bufvar << ".safe_expr_append="
-
else
-
src << bufvar << ".append="
-
end
-
-
if BLOCK_EXPR.match?(code)
-
src << " " << code
-
else
-
src << "(" << code << ");"
-
end
-
end
-
-
9
def add_code(code)
-
flush_newline_if_pending(src)
-
super
-
end
-
-
9
def add_postamble(_)
-
flush_newline_if_pending(src)
-
super
-
end
-
-
9
def flush_newline_if_pending(src)
-
if @newline_pending > 0
-
src << bufvar << ".safe_append='#{"\n" * @newline_pending}'.freeze;"
-
@newline_pending = 0
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
9
module Template::Handlers
-
9
class Html < Raw
-
9
def call(template, source)
-
"ActionView::OutputBuffer.new #{super}"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
9
module Template::Handlers
-
9
class Raw
-
9
def call(template, source)
-
"#{source.inspect}.html_safe;"
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView #:nodoc:
-
# = Action View HTML Template
-
class Template #:nodoc:
-
class HTML #:nodoc:
-
attr_reader :type
-
-
def initialize(string, type = nil)
-
unless type
-
ActiveSupport::Deprecation.warn "ActionView::Template::HTML#initialize requires a type parameter"
-
type = :html
-
end
-
-
@string = string.to_s
-
@type = type
-
end
-
-
def identifier
-
"html template"
-
end
-
-
alias_method :inspect, :identifier
-
-
def to_str
-
ERB::Util.h(@string)
-
end
-
-
def render(*args)
-
to_str
-
end
-
-
def format
-
@type
-
end
-
-
def formats; Array(format); end
-
deprecate :formats
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView #:nodoc:
-
class Template #:nodoc:
-
class Inline < Template #:nodoc:
-
# This finalizer is needed (and exactly with a proc inside another proc)
-
# otherwise templates leak in development.
-
Finalizer = proc do |method_name, mod| # :nodoc:
-
proc do
-
mod.module_eval do
-
remove_possible_method method_name
-
end
-
end
-
end
-
-
def compile(mod)
-
super
-
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView #:nodoc:
-
# = Action View RawFile Template
-
class Template #:nodoc:
-
class RawFile #:nodoc:
-
attr_accessor :type, :format
-
-
def initialize(filename)
-
@filename = filename.to_s
-
extname = ::File.extname(filename).delete(".")
-
@type = Template::Types[extname] || Template::Types[:text]
-
@format = @type.symbol
-
end
-
-
def identifier
-
@filename
-
end
-
-
def render(*args)
-
::File.read(@filename)
-
end
-
-
def formats; Array(format); end
-
deprecate :formats
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "pathname"
-
9
require "active_support/core_ext/class"
-
9
require "active_support/core_ext/module/attribute_accessors"
-
9
require "action_view/template"
-
9
require "thread"
-
9
require "concurrent/map"
-
-
9
module ActionView
-
# = Action View Resolver
-
9
class Resolver
-
# Keeps all information about view path and builds virtual path.
-
9
class Path
-
9
attr_reader :name, :prefix, :partial, :virtual
-
9
alias_method :partial?, :partial
-
-
9
def self.build(name, prefix, partial)
-
virtual = +""
-
virtual << "#{prefix}/" unless prefix.empty?
-
virtual << (partial ? "_#{name}" : name)
-
new name, prefix, partial, virtual
-
end
-
-
9
def initialize(name, prefix, partial, virtual)
-
@name = name
-
@prefix = prefix
-
@partial = partial
-
@virtual = virtual
-
end
-
-
9
def to_str
-
@virtual
-
end
-
9
alias :to_s :to_str
-
end
-
-
9
class PathParser # :nodoc:
-
9
def build_path_regex
-
handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
-
formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
-
locales = "[a-z]{2}(?:-[A-Z]{2})?"
-
variants = "[^.]*"
-
-
%r{
-
\A
-
(?:(?<prefix>.*)/)?
-
(?<partial>_)?
-
(?<action>.*?)
-
(?:\.(?<locale>#{locales}))??
-
(?:\.(?<format>#{formats}))??
-
(?:\+(?<variant>#{variants}))??
-
(?:\.(?<handler>#{handlers}))?
-
\z
-
}x
-
end
-
-
9
def parse(path)
-
@regex ||= build_path_regex
-
match = @regex.match(path)
-
{
-
prefix: match[:prefix] || "",
-
action: match[:action],
-
partial: !!match[:partial],
-
locale: match[:locale]&.to_sym,
-
handler: match[:handler]&.to_sym,
-
format: match[:format]&.to_sym,
-
variant: match[:variant]
-
}
-
end
-
end
-
-
# Threadsafe template cache
-
9
class Cache #:nodoc:
-
9
class SmallCache < Concurrent::Map
-
9
def initialize(options = {})
-
66
super(options.merge(initial_capacity: 2))
-
end
-
end
-
-
# Preallocate all the default blocks for performance/memory consumption reasons
-
9
PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
-
9
PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
-
9
NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
-
9
KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
-
-
# Usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
-
9
NO_TEMPLATES = [].freeze
-
-
9
def initialize
-
33
@data = SmallCache.new(&KEY_BLOCK)
-
33
@query_cache = SmallCache.new
-
end
-
-
9
def inspect
-
"#{to_s[0..-2]} keys=#{@data.size} queries=#{@query_cache.size}>"
-
end
-
-
# Cache the templates returned by the block
-
9
def cache(key, name, prefix, partial, locals)
-
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
-
end
-
-
9
def cache_query(query) # :nodoc:
-
@query_cache[query] ||= canonical_no_templates(yield)
-
end
-
-
9
def clear
-
9
@data.clear
-
9
@query_cache.clear
-
end
-
-
# Get the cache size. Do not call this
-
# method. This method is not guaranteed to be here ever.
-
9
def size # :nodoc:
-
size = 0
-
@data.each_value do |v1|
-
v1.each_value do |v2|
-
v2.each_value do |v3|
-
v3.each_value do |v4|
-
size += v4.size
-
end
-
end
-
end
-
end
-
-
size + @query_cache.size
-
end
-
-
9
private
-
9
def canonical_no_templates(templates)
-
templates.empty? ? NO_TEMPLATES : templates
-
end
-
end
-
-
9
cattr_accessor :caching, default: true
-
-
9
class << self
-
9
alias :caching? :caching
-
end
-
-
9
def initialize
-
33
@cache = Cache.new
-
end
-
-
9
def clear_cache
-
9
@cache.clear
-
end
-
-
# Normalizes the arguments and passes it on to find_templates.
-
9
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
-
locals = locals.map(&:to_s).sort!.freeze
-
-
cached(key, [name, prefix, partial], details, locals) do
-
_find_all(name, prefix, partial, details, key, locals)
-
end
-
end
-
-
9
alias :find_all_anywhere :find_all
-
9
deprecate :find_all_anywhere
-
-
9
def find_all_with_query(query) # :nodoc:
-
@cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
-
end
-
-
9
private
-
9
def _find_all(name, prefix, partial, details, key, locals)
-
find_templates(name, prefix, partial, details, locals)
-
end
-
-
9
delegate :caching?, to: :class
-
-
# This is what child classes implement. No defaults are needed
-
# because Resolver guarantees that the arguments are present and
-
# normalized.
-
9
def find_templates(name, prefix, partial, details, locals = [])
-
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
-
end
-
-
# Handles templates caching. If a key is given and caching is on
-
# always check the cache before hitting the resolver. Otherwise,
-
# it always hits the resolver but if the key is present, check if the
-
# resolver is fresher before returning it.
-
9
def cached(key, path_info, details, locals)
-
name, prefix, partial = path_info
-
-
if key
-
@cache.cache(key, name, prefix, partial, locals) do
-
yield
-
end
-
else
-
yield
-
end
-
end
-
end
-
-
# An abstract class that implements a Resolver with path semantics.
-
9
class PathResolver < Resolver #:nodoc:
-
9
EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
-
9
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
-
-
9
def initialize(pattern = nil)
-
33
if pattern
-
ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead."
-
@pattern = pattern
-
else
-
33
@pattern = DEFAULT_PATTERN
-
end
-
33
@unbound_templates = Concurrent::Map.new
-
33
@path_parser = PathParser.new
-
33
super()
-
end
-
-
9
def clear_cache
-
9
@unbound_templates.clear
-
9
@path_parser = PathParser.new
-
9
super()
-
end
-
-
9
private
-
9
def _find_all(name, prefix, partial, details, key, locals)
-
path = Path.build(name, prefix, partial)
-
query(path, details, details[:formats], locals, cache: !!key)
-
end
-
-
9
def query(path, details, formats, locals, cache:)
-
template_paths = find_template_paths_from_details(path, details)
-
template_paths = reject_files_external_to_app(template_paths)
-
-
template_paths.map do |template|
-
unbound_template =
-
if cache
-
@unbound_templates.compute_if_absent([template, path.virtual]) do
-
build_unbound_template(template, path.virtual)
-
end
-
else
-
build_unbound_template(template, path.virtual)
-
end
-
-
unbound_template.bind_locals(locals)
-
end
-
end
-
-
9
def source_for_template(template)
-
Template::Sources::File.new(template)
-
end
-
-
9
def build_unbound_template(template, virtual_path)
-
handler, format, variant = extract_handler_and_format_and_variant(template)
-
source = source_for_template(template)
-
-
UnboundTemplate.new(
-
source,
-
template,
-
handler,
-
virtual_path: virtual_path,
-
format: format,
-
variant: variant,
-
)
-
end
-
-
9
def reject_files_external_to_app(files)
-
files.reject { |filename| !inside_path?(@path, filename) }
-
end
-
-
9
def find_template_paths_from_details(path, details)
-
if path.name.include?(".")
-
ActiveSupport::Deprecation.warn("Rendering actions with '.' in the name is deprecated: #{path}")
-
end
-
-
query = build_query(path, details)
-
find_template_paths(query)
-
end
-
-
9
def find_template_paths(query)
-
Dir[query].uniq.reject do |filename|
-
File.directory?(filename) ||
-
# deals with case-insensitive file systems.
-
!File.fnmatch(query, filename, File::FNM_EXTGLOB)
-
end
-
end
-
-
9
def inside_path?(path, filename)
-
filename = File.expand_path(filename)
-
path = File.join(path, "")
-
filename.start_with?(path)
-
end
-
-
# Helper for building query glob string based on resolver's pattern.
-
9
def build_query(path, details)
-
query = @pattern.dup
-
-
prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
-
query.gsub!(/:prefix(\/)?/, prefix)
-
-
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
-
query.gsub!(":action", partial)
-
-
details.each do |ext, candidates|
-
if ext == :variants && candidates == :any
-
query.gsub!(/:#{ext}/, "*")
-
else
-
query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
-
end
-
end
-
-
File.expand_path(query, @path)
-
end
-
-
9
def escape_entry(entry)
-
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
-
end
-
-
# Extract handler, formats and variant from path. If a format cannot be found neither
-
# from the path, or the handler, we should return the array of formats given
-
# to the resolver.
-
9
def extract_handler_and_format_and_variant(path)
-
details = @path_parser.parse(path)
-
-
handler = Template.handler_for_extension(details[:handler])
-
format = details[:format] || handler.try(:default_format)
-
variant = details[:variant]
-
-
# Template::Types[format] and handler.default_format can return nil
-
[handler, format, variant]
-
end
-
end
-
-
# A resolver that loads files from the filesystem.
-
9
class FileSystemResolver < PathResolver
-
9
attr_reader :path
-
-
9
def initialize(path, pattern = nil)
-
33
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
-
33
super(pattern)
-
33
@path = File.expand_path(path)
-
end
-
-
9
def to_s
-
@path.to_s
-
end
-
9
alias :to_path :to_s
-
-
9
def eql?(resolver)
-
self.class.equal?(resolver.class) && to_path == resolver.to_path
-
end
-
9
alias :== :eql?
-
end
-
-
# An Optimized resolver for Rails' most common case.
-
9
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
-
9
def initialize(path)
-
27
super(path)
-
end
-
-
9
private
-
9
def find_candidate_template_paths(path)
-
# Instead of checking for every possible path, as our other globs would
-
# do, scan the directory for files with the right prefix.
-
query = "#{escape_entry(File.join(@path, path))}*"
-
-
Dir[query].reject do |filename|
-
File.directory?(filename)
-
end
-
end
-
-
9
def find_template_paths_from_details(path, details)
-
if path.name.include?(".")
-
# Fall back to the unoptimized resolver, which will warn
-
return super
-
end
-
-
candidates = find_candidate_template_paths(path)
-
-
regex = build_regex(path, details)
-
-
candidates.uniq.reject do |filename|
-
# This regex match does double duty of finding only files which match
-
# details (instead of just matching the prefix) and also filtering for
-
# case-insensitive file systems.
-
!regex.match?(filename) ||
-
File.directory?(filename)
-
end.sort_by do |filename|
-
# Because we scanned the directory, instead of checking for files
-
# one-by-one, they will be returned in an arbitrary order.
-
# We can use the matches found by the regex and sort by their index in
-
# details.
-
match = filename.match(regex)
-
EXTENSIONS.keys.map do |ext|
-
if ext == :variants && details[ext] == :any
-
match[ext].nil? ? 0 : 1
-
elsif match[ext].nil?
-
# No match should be last
-
details[ext].length
-
else
-
found = match[ext].to_sym
-
details[ext].index(found)
-
end
-
end
-
end
-
end
-
-
9
def build_regex(path, details)
-
query = Regexp.escape(File.join(@path, path))
-
exts = EXTENSIONS.map do |ext, prefix|
-
match =
-
if ext == :variants && details[ext] == :any
-
".*?"
-
else
-
arr = details[ext].compact
-
arr.uniq!
-
arr.map! { |e| Regexp.escape(e) }
-
arr.join("|")
-
end
-
prefix = Regexp.escape(prefix)
-
"(#{prefix}(?<#{ext}>#{match}))?"
-
end.join
-
-
%r{\A#{query}#{exts}\z}
-
end
-
end
-
-
# The same as FileSystemResolver but does not allow templates to store
-
# a virtual path since it is invalid for such resolvers.
-
9
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
-
9
private_class_method :new
-
-
9
def self.instances
-
3
[new(""), new("/")]
-
end
-
-
9
def build_unbound_template(template, _)
-
super(template, nil)
-
end
-
-
9
def reject_files_external_to_app(files)
-
files
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
class Template
-
module Sources
-
extend ActiveSupport::Autoload
-
-
eager_autoload do
-
autoload :File
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView
-
class Template
-
module Sources
-
class File
-
def initialize(filename)
-
@filename = filename
-
end
-
-
def to_s
-
::File.binread @filename
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
module ActionView #:nodoc:
-
# = Action View Text Template
-
class Template #:nodoc:
-
class Text #:nodoc:
-
attr_accessor :type
-
-
def initialize(string)
-
@string = string.to_s
-
end
-
-
def identifier
-
"text template"
-
end
-
-
alias_method :inspect, :identifier
-
-
def to_str
-
@string
-
end
-
-
def render(*args)
-
to_str
-
end
-
-
def format
-
:text
-
end
-
-
def formats; Array(format); end
-
deprecate :formats
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
3
require "active_support/core_ext/module/attribute_accessors"
-
-
3
module ActionView
-
3
class Template #:nodoc:
-
3
class Types
-
3
class Type
-
3
SET = Struct.new(:symbols).new([ :html, :text, :js, :css, :xml, :json ])
-
-
3
def self.[](type)
-
if type.is_a?(self)
-
type
-
else
-
new(type)
-
end
-
end
-
-
3
attr_reader :symbol
-
-
3
def initialize(symbol)
-
@symbol = symbol.to_sym
-
end
-
-
3
def to_s
-
@symbol.to_s
-
end
-
3
alias to_str to_s
-
-
3
def ref
-
@symbol
-
end
-
3
alias to_sym ref
-
-
3
def ==(type)
-
@symbol == type.to_sym unless type.blank?
-
end
-
end
-
-
3
cattr_accessor :type_klass
-
-
3
def self.delegate_to(klass)
-
9
self.type_klass = klass
-
end
-
-
3
delegate_to Type
-
-
3
def self.[](type)
-
type_klass[type]
-
end
-
-
3
def self.symbols
-
type_klass::SET.symbols
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
6
require "active_support/core_ext/module/redefine_method"
-
6
require "action_controller"
-
6
require "action_controller/test_case"
-
6
require "action_view"
-
-
6
require "rails-dom-testing"
-
-
6
module ActionView
-
# = Action View Test Case
-
6
class TestCase < ActiveSupport::TestCase
-
6
class TestController < ActionController::Base
-
6
include ActionDispatch::TestProcess
-
-
6
attr_accessor :request, :response, :params
-
-
6
class << self
-
6
attr_writer :controller_path
-
end
-
-
6
def controller_path=(path)
-
self.class.controller_path = (path)
-
end
-
-
6
def initialize
-
super
-
self.class.controller_path = ""
-
@request = ActionController::TestRequest.create(self.class)
-
@response = ActionDispatch::TestResponse.new
-
-
@request.env.delete("PATH_INFO")
-
@params = ActionController::Parameters.new
-
end
-
end
-
-
6
module Behavior
-
6
extend ActiveSupport::Concern
-
-
6
include ActionDispatch::Assertions, ActionDispatch::TestProcess
-
6
include Rails::Dom::Testing::Assertions
-
6
include ActionController::TemplateAssertions
-
6
include ActionView::Context
-
-
6
include ActionDispatch::Routing::PolymorphicRoutes
-
-
6
include AbstractController::Helpers
-
6
include ActionView::Helpers
-
6
include ActionView::RecordIdentifier
-
6
include ActionView::RoutingUrlFor
-
-
6
include ActiveSupport::Testing::ConstantLookup
-
-
6
delegate :lookup_context, to: :controller
-
6
attr_accessor :controller, :output_buffer, :rendered
-
-
6
module ClassMethods
-
6
def tests(helper_class)
-
81
case helper_class
-
when String, Symbol
-
6
self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
-
when Module
-
75
self.helper_class = helper_class
-
end
-
end
-
-
6
def determine_default_helper_class(name)
-
determine_constant_from_test_name(name) do |constant|
-
Module === constant && !(Class === constant)
-
end
-
end
-
-
6
def helper_method(*methods)
-
# Almost a duplicate from ActionController::Helpers
-
6
methods.flatten.each do |method|
-
6
_helpers.module_eval <<-end_eval, __FILE__, __LINE__ + 1
-
def #{method}(*args, &block) # def current_user(*args, &block)
-
_test_case.send(:'#{method}', *args, &block) # _test_case.send(:'current_user', *args, &block)
-
end # end
-
ruby2_keywords(:'#{method}') if respond_to?(:ruby2_keywords, true)
-
end_eval
-
end
-
end
-
-
6
attr_writer :helper_class
-
-
6
def helper_class
-
@helper_class ||= determine_default_helper_class(name)
-
end
-
-
6
def new(*)
-
include_helper_modules!
-
super
-
end
-
-
6
private
-
6
def include_helper_modules!
-
helper(helper_class) if helper_class
-
include _helpers
-
end
-
end
-
-
6
def setup_with_controller
-
@controller = ActionView::TestCase::TestController.new
-
@request = @controller.request
-
@view_flow = ActionView::OutputFlow.new
-
# empty string ensures buffer has UTF-8 encoding as
-
# new without arguments returns ASCII-8BIT encoded buffer like String#new
-
@output_buffer = ActiveSupport::SafeBuffer.new ""
-
@rendered = +""
-
-
make_test_case_available_to_view!
-
say_no_to_protect_against_forgery!
-
end
-
-
6
def config
-
@controller.config if @controller.respond_to?(:config)
-
end
-
-
6
def render(options = {}, local_assigns = {}, &block)
-
view.assign(view_assigns)
-
@rendered << output = view.render(options, local_assigns, &block)
-
output
-
end
-
-
6
def rendered_views
-
@_rendered_views ||= RenderedViewsCollection.new
-
end
-
-
6
def _routes
-
@controller._routes if @controller.respond_to?(:_routes)
-
end
-
-
# Need to experiment if this priority is the best one: rendered => output_buffer
-
6
class RenderedViewsCollection
-
6
def initialize
-
@rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
-
end
-
-
6
def add(view, locals)
-
@rendered_views[view] ||= []
-
@rendered_views[view] << locals
-
end
-
-
6
def locals_for(view)
-
@rendered_views[view]
-
end
-
-
6
def rendered_views
-
@rendered_views.keys
-
end
-
-
6
def view_rendered?(view, expected_locals)
-
locals_for(view).any? do |actual_locals|
-
expected_locals.all? { |key, value| value == actual_locals[key] }
-
end
-
end
-
end
-
-
6
included do
-
6
setup :setup_with_controller
-
6
ActiveSupport.run_load_hooks(:action_view_test_case, self)
-
end
-
-
6
private
-
# Need to experiment if this priority is the best one: rendered => output_buffer
-
6
def document_root_element
-
Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root
-
end
-
-
6
def say_no_to_protect_against_forgery!
-
_helpers.module_eval do
-
silence_redefinition_of_method :protect_against_forgery?
-
def protect_against_forgery?
-
false
-
end
-
end
-
end
-
-
6
def make_test_case_available_to_view!
-
test_case_instance = self
-
_helpers.module_eval do
-
unless private_method_defined?(:_test_case)
-
define_method(:_test_case) { test_case_instance }
-
private :_test_case
-
end
-
end
-
end
-
-
6
module Locals
-
6
attr_accessor :rendered_views
-
-
6
def render(options = {}, local_assigns = {})
-
case options
-
when Hash
-
if block_given?
-
rendered_views.add options[:layout], options[:locals]
-
elsif options.key?(:partial)
-
rendered_views.add options[:partial], options[:locals]
-
end
-
else
-
rendered_views.add options, local_assigns
-
end
-
-
super
-
end
-
end
-
-
# The instance of ActionView::Base that is used by +render+.
-
6
def view
-
@view ||= begin
-
view = @controller.view_context
-
view.singleton_class.include(_helpers)
-
view.extend(Locals)
-
view.rendered_views = rendered_views
-
view.output_buffer = output_buffer
-
view
-
end
-
end
-
-
6
alias_method :_view, :view
-
-
6
INTERNAL_IVARS = [
-
:@NAME,
-
:@failures,
-
:@assertions,
-
:@__io__,
-
:@_assertion_wrapped,
-
:@_assertions,
-
:@_result,
-
:@_routes,
-
:@controller,
-
:@_layouts,
-
:@_files,
-
:@_rendered_views,
-
:@method_name,
-
:@output_buffer,
-
:@_partials,
-
:@passed,
-
:@rendered,
-
:@request,
-
:@routes,
-
:@tagged_logger,
-
:@_templates,
-
:@options,
-
:@test_passed,
-
:@view,
-
:@view_context_class,
-
:@view_flow,
-
:@_subscribers,
-
:@html_document
-
]
-
-
6
def _user_defined_ivars
-
instance_variables - INTERNAL_IVARS
-
end
-
-
# Returns a Hash of instance variables and their values, as defined by
-
# the user in the test case, which are then assigned to the view being
-
# rendered. This is generally intended for internal use and extension
-
# frameworks.
-
6
def view_assigns
-
Hash[_user_defined_ivars.map do |ivar|
-
[ivar[1..-1].to_sym, instance_variable_get(ivar)]
-
end]
-
end
-
-
6
def method_missing(selector, *args)
-
begin
-
routes = @controller.respond_to?(:_routes) && @controller._routes
-
rescue
-
# Don't call routes, if there is an error on _routes call
-
end
-
-
if routes &&
-
(routes.named_routes.route_defined?(selector) ||
-
routes.mounted_helpers.method_defined?(selector))
-
@controller.__send__(selector, *args)
-
else
-
super
-
end
-
end
-
-
6
def respond_to_missing?(name, include_private = false)
-
begin
-
routes = defined?(@controller) && @controller.respond_to?(:_routes) && @controller._routes
-
rescue
-
# Don't call routes, if there is an error on _routes call
-
end
-
-
routes &&
-
(routes.named_routes.route_defined?(name) ||
-
routes.mounted_helpers.method_defined?(name))
-
end
-
end
-
-
6
include Behavior
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require "action_view/template/resolver"
-
-
9
module ActionView #:nodoc:
-
# Use FixtureResolver in your tests to simulate the presence of files on the
-
# file system. This is used internally by Rails' own test suite, and is
-
# useful for testing extensions that have no way of knowing what the file
-
# system will look like at runtime.
-
9
class FixtureResolver < OptimizedFileSystemResolver
-
9
def initialize(hash = {}, pattern = nil)
-
6
super("")
-
6
if pattern
-
ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead."
-
@pattern = pattern
-
end
-
6
@hash = hash
-
6
@path = ""
-
end
-
-
9
def data
-
@hash
-
end
-
-
9
def to_s
-
@hash.keys.join(", ")
-
end
-
-
9
private
-
9
def find_candidate_template_paths(path)
-
@hash.keys.select do |fixture|
-
fixture.start_with?(path.virtual)
-
end.map do |fixture|
-
"/#{fixture}"
-
end
-
end
-
-
9
def source_for_template(template)
-
@hash[template[1..template.size]]
-
end
-
end
-
-
9
class NullResolver < PathResolver
-
9
def query(path, exts, _, locals, cache:)
-
handler, format, variant = extract_handler_and_format_and_variant(path)
-
[ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant, locals: locals)]
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
require "concurrent/map"
-
-
module ActionView
-
class UnboundTemplate
-
def initialize(source, identifier, handler, options)
-
@source = source
-
@identifier = identifier
-
@handler = handler
-
@options = options
-
-
@templates = Concurrent::Map.new(initial_capacity: 2)
-
end
-
-
def bind_locals(locals)
-
@templates[locals] ||= build_template(locals)
-
end
-
-
private
-
def build_template(locals)
-
options = @options.merge(locals: locals)
-
Template.new(
-
@source,
-
@identifier,
-
@handler,
-
**options
-
)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
9
require_relative "gem_version"
-
-
9
module ActionView
-
# Returns the version of the currently loaded ActionView as a <tt>Gem::Version</tt>
-
9
def self.version
-
gem_version
-
end
-
end
-
# frozen_string_literal: true
-
-
9
module ActionView
-
9
module ViewPaths
-
9
extend ActiveSupport::Concern
-
-
9
included do
-
24
ViewPaths.set_view_paths(self, ActionView::PathSet.new.freeze)
-
end
-
-
9
delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=,
-
:locale, :locale=, to: :lookup_context
-
-
9
module ClassMethods
-
9
def _view_paths
-
12
ViewPaths.get_view_paths(self)
-
end
-
-
9
def _view_paths=(paths)
-
36
ViewPaths.set_view_paths(self, paths)
-
end
-
-
9
def _prefixes # :nodoc:
-
@_prefixes ||= begin
-
return local_prefixes if superclass.abstract?
-
-
local_prefixes + superclass._prefixes
-
end
-
end
-
-
# Append a path to the list of view paths for this controller.
-
#
-
# ==== Parameters
-
# * <tt>path</tt> - If a String is provided, it gets converted into
-
# the default view path. You may also provide a custom view path
-
# (see ActionView::PathSet for more information)
-
9
def append_view_path(path)
-
6
self._view_paths = view_paths + Array(path)
-
end
-
-
# Prepend a path to the list of view paths for this controller.
-
#
-
# ==== Parameters
-
# * <tt>path</tt> - If a String is provided, it gets converted into
-
# the default view path. You may also provide a custom view path
-
# (see ActionView::PathSet for more information)
-
9
def prepend_view_path(path)
-
self._view_paths = ActionView::PathSet.new(Array(path) + view_paths)
-
end
-
-
# A list of all of the default view paths for this controller.
-
9
def view_paths
-
12
_view_paths
-
end
-
-
# Set the view paths.
-
#
-
# ==== Parameters
-
# * <tt>paths</tt> - If a PathSet is provided, use that;
-
# otherwise, process the parameter into a PathSet.
-
9
def view_paths=(paths)
-
30
self._view_paths = ActionView::PathSet.new(Array(paths))
-
end
-
-
9
private
-
# Override this method in your controller if you want to change paths prefixes for finding views.
-
# Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
-
9
def local_prefixes
-
[controller_path]
-
end
-
end
-
-
# :stopdoc:
-
9
@all_view_paths = {}
-
-
9
def self.get_view_paths(klass)
-
12
@all_view_paths[klass] || get_view_paths(klass.superclass)
-
end
-
-
9
def self.set_view_paths(klass, paths)
-
60
@all_view_paths[klass] = paths
-
end
-
-
9
def self.all_view_paths
-
3
@all_view_paths.values.uniq
-
end
-
# :startdoc:
-
-
# The prefixes used in render "foo" shortcuts.
-
9
def _prefixes # :nodoc:
-
self.class._prefixes
-
end
-
-
# <tt>LookupContext</tt> is the object responsible for holding all
-
# information required for looking up templates, i.e. view paths and
-
# details. Check <tt>ActionView::LookupContext</tt> for more information.
-
9
def lookup_context
-
@_lookup_context ||=
-
ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes)
-
end
-
-
9
def details_for_lookup
-
{}
-
end
-
-
# Append a path to the list of view paths for the current <tt>LookupContext</tt>.
-
#
-
# ==== Parameters
-
# * <tt>path</tt> - If a String is provided, it gets converted into
-
# the default view path. You may also provide a custom view path
-
# (see ActionView::PathSet for more information)
-
9
def append_view_path(path)
-
lookup_context.view_paths.push(*path)
-
end
-
-
# Prepend a path to the list of view paths for the current <tt>LookupContext</tt>.
-
#
-
# ==== Parameters
-
# * <tt>path</tt> - If a String is provided, it gets converted into
-
# the default view path. You may also provide a custom view path
-
# (see ActionView::PathSet for more information)
-
9
def prepend_view_path(path)
-
lookup_context.view_paths.unshift(*path)
-
end
-
end
-
end